Android: Exploit listView choiceMode with item highlighting

I lost last 2 hours to find out how to have a list view with selectable items exploiting the interface it provides in order to avoid useless and unsafe coding.

Selectable items
Makes items selectable is easy, setting it up in the xml is enough:

<ListView
        android:id="@android:id/list"
        android:choiceMode="multipleChoice"
       />

Once you did that your listView automatically handles items selection and deselection.
You can retrieve the selected items with one of the following methods:

list.getCheckedItemPosition();
list.getCheckedItemCount();
list.getCheckedItemPositions();
list.getCheckedItemIds();

For instance to get the common behaviour for which you show a custom ActionBar
if some elements are selected, you have just to check in the item click listener:

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   if(list.getCheckedItemCount() > 0) {
     //Show custom action bar
   }else{
     //Show default action bar
   }
}

Visual feedbacks
The hard part is to properly implements visual feedbacks on selected items. The right way (or at least the one I expected it to be) is to use an xml Drawable with a selector with one item for each state (i.e. selected/default)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@color/palette_accent"
        android:state_selected="true"
     />
    <item
        android:drawable="@color/palette_accent"
        android:state_focused="true"
        />
    <item
        android:drawable="@color/palette_accent"
        android:state_pressed="true"
        />
    <item
        android:drawable="@color/palette_accent"
        android:state_checked="true"
        />

    <item
        android:drawable="@color/palette_content"
        />

</selector>

Unfortunately none of above selected states are triggered when we expected (on selected items).

Some considerations before the solution:

  • If you programmatically change the list item view state (i.e. to select state) you may lost (or have to handle) everything when the ListView recycles/throws your View object;
  • I tried to use listSelector xml property but if it is not onTop it stays hided by the default list item background. Otherwise it completely hide the list item content;
  • ListView has it own interface to handle all this stuff, why not to use that?

From the methods name to get the selected elements you can guess that ListView uses Checkable interface to keep trace of selected items, the problem is that most of the root elements of a list item doesn’t implement such interface (pretty annoying). So the solution is to extend the layout you use in order to make it implements Checkable. Here follow an example with RelativeLayout.

public class CheckableRelativeLayout extends RelativeLayout implements Checkable{
   private boolean checked = false;

   public CheckableRelativeLayout(Context context) {
      super(context);
   }

   public CheckableRelativeLayout(Context context, AttributeSet attrs){
      super(context, attrs);
   }

   public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr){
      super(context, attrs,defStyleAttr);
   }

   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
   public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
      super(context, attrs, defStyleAttr, defStyleRes);
   }

   @Override
   public void setChecked(boolean checked) {
      this.checked = checked;
      refreshDrawableState();
   }

   @Override
   public boolean isChecked() {
      return checked;
   }

   @Override
   public void toggle() {
      setChecked(!checked);
   }

   private static final int[] STATE_CHECKABLE = {android.R.attr.state_pressed};
   @Override
   protected int[] onCreateDrawableState(int extraSpace) {
      int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
      if (checked) mergeDrawableStates(drawableState, STATE_CHECKABLE);

      return drawableState;
   }
}

It’s a pretty trivial implementation, not much to say about it. Anyway once done that in your listitem layout replace the root RelativeLayout with your.package.CheckableRelativeLayout, set as background an xml Drawable selector with state for pressed, checked and default. Then you should have the desired behaviour.

While I was Googling for a solution I also gave a look to OnItemSelectedListener hoping to find something useful, although I wasn’t able to find out how it works. I never get the callback called.

Credits: this article comes from a post found on stackoverflow which briefly explain the same thing. Anyway since I found really hard to find it googling I wrote a new article.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *