Tearing my hair out trying to get an Android ListView to do what I want.
I want to have a ListView in single choice mode with a custom row layout that has a different background color for selected, pressed and checked (i.e. the choice is shown by a color rather than a check mark - this is what I would normally call the "selection" but selection in android seems line I'm about to choose before I press it)
I thought of trying a background selector with the three states in it. It works fine for state_selected and state_pressed, but not state_checked. So I created a CheckableRelativeLayout that extends RelativeLayout and implements Checkable and used for the view of each row.
A simplified version is shown here:
<my.package.CheckableRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/bkg_selector">
>
<ImageView android:id="@+id/animage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
/>
</my.package.CheckableRelativeLayout>
bkg_selector looks like
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/purple" />
<item android:state_checked="true" android:drawable="@drawable/red" />
<item android:state_selected="true" android:drawable="@drawable/darkpurple" />
<item android:drawable="@drawable/black" />
</selector>
The colors are defined elsewhere.
This still didn't work. So in the custom ListAdapter I tracked the "checked" row and tried (in getView)
if( position == checkedPosition ) ret.getBackground().setState(CHECKED_STATE_SET);
And it STILL doesn't work. How can I get it to do what I want?
You need to override onCreateDrawableState in your CheckableRelativeLayout and set Clickable="true" for it. My code for LinearLayout:
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private boolean checked = false;
public CheckableLinearLayout(Context context) {
super(context, null);
}
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
private static final int[] CheckedStateSet = {
R.attr.state_checked
};
public void setChecked(boolean b) {
checked = b;
}
public boolean isChecked() {
return checked;
}
public void toggle() {
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();
}
Better than that, instead of setting clickable=true and overriding CheckableLinearLayout's performClick(), keep Pavel's suggestion for overriding onCreateDrawableState and replace CheckableLinearLayout's setChecked() by the following:
private final List<Checkable> mCheckableViews = new ArrayList<Checkable>();
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int childCount = getChildCount();
findCheckableChildren(this);
}
private void findCheckableChildren(View v) {
if (v instanceof Checkable && v instanceof ViewGroup) {
mCheckableViews.add((Checkable) v);
}
if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
final int childCount = vg.getChildCount();
for (int i = 0; i < childCount; ++i) {
findCheckableChildren(vg.getChildAt(i));
}
}
}
@Override
public void setChecked(boolean checked) {
mChecked = checked;
for (Checkable c : mCheckableViews) {
c.setChecked(checked);
}
refreshDrawableState();
}
It'll avoid problems on click and long click callbacks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With