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 yourView
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.