Weird behavior of ListView item and state selector background drawable
I am having some very strange ListView behavior when using a StateListDrawable as the background. I've tried to follow the answer to this post, as I wasn't getting the state_checked state to work, but now my ListView is going crazy.
When I click on an item, it doesn't immediately change color to the state_checked item in the selector. After clicking around a bit though, many of the views will suddenly switch to the state_checked background. It's seemingly random.
Here is my state selector xml code:
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<gradient
android:startColor="@color/grey"
android:endColor="@color/darkgrey"
android:angle="270" />
<stroke
android:width="0dp"
android:color="@color/grey05" />
<corners
android:radius="0dp" />
<padding
android:left="10sp"
android:top="10sp"
android:right="10sp"
android:bottom="10sp" />
</shape>
</item>
<item android:state_focused="true" >
<shape>
<gradient
android:endColor="@color/orange4"
android:startColor="@color/orange5"
android:angle="270" />
<stroke
android:width="0dp"
android:color="@color/grey05" />
<corners
android:radius="0dp" />
<padding
android:left="10sp"
android:top="10sp"
android:right="10sp"
android:bottom="10sp" />
</shape>
</item>
<item android:state_checked="true">
<shape>
<gradient
android:endColor="@color/brown2"
android:startColor="@color/brown1"
android:angle="270" />
<stroke
android:width="0dp"
android:color="@color/grey05" />
<corners
android:radius="0dp" />
<padding
android:left="10sp"
android:top="10sp"
android:right="10sp"
android:bottom="10sp" />
</shape>
</item>
<item android:state_selected="true">
<shape>
<gradient
android:endColor="@color/brown2"
android:startColor="@color/brown1"
android:angle="270" />
<stroke
android:width="0dp"
android:color="@color/grey05" />
<corners
android:radius="0dp" />
<padding
android:left="10sp"
android:top="10sp"
android:right="10sp"
android:bottom="10sp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="@color/white"
android:endColor="@color/white2"
android:angle="270" />
<stroke
android:width="0dp"
android:color="@color/grey05" />
<corners
android:radius="0dp" />
<padding
android:left="10sp"
android:top="10sp"
android:right="10sp"
android:bottom="10sp" />
</shape>
</item>
</selector>
And here is my .java class for my Custom view implementing checkable:
public class Entry extends LinearLayout implements Checkable {
public Entry(Context context) {
开发者_高级运维super(context, null);
// Inflate this view
LayoutInflater temp = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
temp.inflate(R.layout.entry, this, true);
initViews();
}
private static final int[] CheckedStateSet = {
android.R.attr.state_checked
};
private void initViews() {
this.setBackgroundResource(R.drawable.listview_row);
}
public boolean isChecked() {
return _checked;
}
public void toggle() {
_checked = !_checked;
}
public void setChecked(boolean checked) {
_checked = checked;
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CheckedStateSet);
}
return drawableState;
}
@Override
public boolean performClick() {
toggle();
return super.performClick();
}
}
I've poked around for a few hours trying to figure it out, but unfortunately must concede to asking for help. Can anyone see something wrong with the code above that would cause the ListView to behave strangely on the items? I can post more code as well, if needed.
When working with ListView
it is very important to always keep in mind that the views are the presentation and the adapter is the data model.
This means that all of your state should be in the adapter (the data model), not in the views.
From what I can tell of your code, you have a view that is showing a check state, and that state is in the view not in the adapter. That is, when the user clicks on that item in the list, the view being used to display its item has its internal checked state changed to toggle what is shown to the user.
But since the view is not the data model, this state you are playing with here is transient and not actually associated with the adapter item being clicked.
The most obvious problem this causes comes in with view recycling. When you scroll through a ListView
, when items scroll off the end and new ones appear at the bottom, the views used to display the old items are re-used to display the new ones. This is much more efficient than having to inflate a new item view hierarchy every time a new item is shown.
Because you have your state in the view, when this recycling happens that state in the view gets randomly re-associated with some new item. This can happen in many cases, not just scrolling.
The solution is to put your check state in the adapter, and implement Adapter.getView()
to set the checked state of the view based on the state you now have in the adapter. That way whenever a view is recycled (and getView()
called to bind the new data row to it), you will update its checked state to correctly follow the new data it is displaying.
I don't think the problem is coming from the code above. I have had this problem before and it had to do with recycling of the views in the listview. This may be the case for you if your list continues off the screen. If this is the case, a good way to fix it is to store the states of the items in a list so you can keep track of them and base their states off the list you created. Take a look at this and this for more information on recycling of the views.
When doing something similar with a fairly complex set of ListViews I added an extra list to the adapter and added the position of clicked items to it. getView(...) then inflates / recycles the view and just before it finishes checks the state of the item, and the internal adapter state to decide which background to apply.
I also setup the state list xml file to make the background transparent when currently pressed so the selector is visible, it works a treat.
精彩评论