如何让Android ListView项目选择器使用state_checked

23

我在尝试让一个Android ListView按我的意愿工作,但却苦于无法实现。

我想要一个单选模式的ListView,其中每个自定义行布局在被选中、按下和勾选时有不同的背景颜色(即通过颜色来表示选择,而不是用勾选标记 - 这通常是我所说的“选择”,但在Android中似乎是在我按下之前就选择了)

我想尝试使用一个包含三种状态的背景选择器。对于state_selected和state_pressed状态,它可以正常工作,但对于state_checked状态,则不能正常工作。因此,我创建了一个CheckableRelativeLayout,它继承了RelativeLayout并实现了Checkable接口,并用于每个行的视图。

以下是一个简化版本:

<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 看起来像
<?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>

颜色在其他地方定义。

这仍然没有起作用。所以在自定义的ListAdapter中,我跟踪了“选中”的行并尝试(在getView中)

如果(位置== checkedPosition) ret.getBackground()。setState(CHECKED_STATE_SET);

它仍然不起作用。我怎样才能让它按照我的意愿工作?

3个回答

44
你需要在CheckableRelativeLayout中覆盖onCreateDrawableState方法并将Clickable设置为"true"。 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();
}

谢谢 - 我会尝试一下,看看它是否有效(得挖出我的代码来适应它!) - MrPurpleStreak
2
更好的方法是,不要设置clickable=true并覆盖CheckableLinearLayout的performClick(),而是覆盖以下内容:@Override public void setChecked(boolean checked) { mChecked = checked; for (Checkable c : mCheckableViews) { c.setChecked(checked); } refreshDrawableState(); } - Flávio Faria
2
@pavel-bakshy:在 setCheckedtoggle 中,您忘记调用 refreshDrawableState - user1614309
@Pavel Bakshy 谢谢,这是唯一一个在我调用 setCheckedtoggle 结束时调用 refreshDrawableState 后能正常工作的解决方案。 - Walid Ammar

10
更好的做法是,不要设置clickable=true并覆盖CheckableLinearLayout的performClick()方法,而是按照Pavel的建议重写onCreateDrawableState,并使用以下代码替换CheckableLinearLayout的setChecked()方法:
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();
    }

这将避免在点击和长按回调时发生问题。


1
你从哪里获取mCheckableViews? - Edu Zamora

0

在我看来,使用自定义适配器会更容易:

class CustomAdapter extends ArrayAdapter<CustomRowItem> {
    Context context;

    public CustomAdapter(Context context, int resourceId, List<CustomRowItem> items) {
        super(context, resourceId, items);
        this.context = context;
    }

    private class ViewHolder {
        TextView txt;
        View layout;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        CustomRowItem rowItem = getItem(position);

        LayoutInflater mInflater = (LayoutInflater) context
            .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.сustom_list, null);
            holder = new ViewHolder();
            holder.txt = (TextView) convertView.findViewById(R.id.сustom_list_txt);
            holder.layout = convertView.findViewById(R.id.сustom_list_layout);
            convertView.setTag(holder);
        } else
            holder = (ViewHolder) convertView.getTag();

        holder.txt.setText(rowItem.getText());
        if(rowItem.isChecked())
            holder.layout.setBackgroundColor(-16720999); //color for checked
        else
            holder.layout.setBackgroundColor(0); //color for unchecked

        return convertView;
    }

}

class CustomRowItem {
    private boolean value;
    private String text;

    public CustomRowItem(String text, boolean value) {
        this.text = text;
        this.value = value;
    }
    public boolean isChecked() {
        return value;
    }
    public void setChecked(boolean checked) {
        value = checked;
    }
    public String getText() {
        return text;
    }
    void setText(String text) {
        this.text = text;
    }
} 

сustom_list.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp"
    android:id="@+id/сustom_list_layout" 
    android:orientation="vertical" >
    <TextView
        android:id="@+id/сustom_list_txt"         
        android:textSize="20sp"
        android:layout_width="fill_parent"         
        android:layout_height="fill_parent"/>
</LinearLayout>

使用方法:

public class ExampleAct extends Activity {

    final List<CustomRowItem> list = new ArrayList<CustomRowItem>();
    CustomAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.example);

        ListView listView=(ListView)findViewById(R.id.listView);

        adapter = new CustomAdapter(this, R.layout.сustom_list, list);
        listView.setAdapter(adapter);

        list.add(new CustomRowItem("unchecked item",false));
        list.add(new CustomRowItem("checked item",true));

        adapter.notifyDataSetChanged();

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { //unchecked on click
            @Override
            public void onItemClick(AdapterView<?> parent, View itemClicked, int index, long id) {
                if(list.get(index).isChecked()) {
                    list.get(index).setChecked(false); //uncheck
                    adapter.notifyDataSetChanged();
                } else {
                    // other actions
                }

        });

        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { //checked on long click
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View itemClicked, int index, long id) {
                list.get(index).setChecked(true); //check
                adapter.notifyDataSetChanged();
                return true; // or false for calling context menu
            }
        });

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接