ListView单选和多选模式完全解析

有很多人在使用listView设置单选和多选的时候不知如何下手,或者从网上找了一些例子,费很大劲才看懂,但又觉得人家写的太过复杂。其中网上很多的demo的中心思想是找到要选择的cell,然后使用adapter.notifyDataSetChanged(),来达到单选或者多选的效果,当然这是一种解决办法;还有一种思想是,让adapter中的View实现Checkable接口,然后达到效果,这是一种非常简便的方式,但网上的那些例子又都没有说出之所以然,所以很多人疑问为什么我的View要实现Checkable。下面我将一一说明。

首先,要明确两点,要实现单选或者多选:

1.listview设置选择模式,即:

public void setChoiceMode(int choiceMode)

模式分为:

/**
 * Normal list that does not indicate choices
 */
public static final int CHOICE_MODE_NONE = 0;

/**
 * The list allows up to one choice
 */
public static final int CHOICE_MODE_SINGLE = 1;

/**
 * The list allows multiple choices
 */
public static final int CHOICE_MODE_MULTIPLE = 2;

/**
 * The list allows multiple choices in a modal selection mode
 */
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;

这四种模式。当你设置完模式后,其实你的ListView已经是单选模式或者多选模式了,但当我们点击时为什么没有反应呢,看第二条。

2.ViewHolder中的控件实现Checkable接口。

这里解释一下为什么要实现Checkable接口,首先看看Listview类中的一个方法:

/**
 * Add a view as a child and make sure it is measured (if necessary) and
 * positioned properly.
 *
 * @param child The view to add
 * @param position The position of this child
 * @param y The y position relative to which this view will be positioned
 * @param flowDown If true, align top edge to y. If false, align bottom
 *        edge to y.
 * @param childrenLeft Left edge where children should be positioned
 * @param selected Is this position selected?
 * @param recycled Has this view been pulled from the recycle bin? If so it
 *        does not need to be remeasured.
 */
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {...}

我们不用管listview什么时候调用的setupChild这个方法,我们只需知道当listview列表展示时,它一定调用了该方法,其中方法中有一段代码:

if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
    if (child instanceof Checkable) {
        ((Checkable) child).setChecked(mCheckStates.get(position));
    } else if (getContext().getApplicationInfo().targetSdkVersion
            >= android.os.Build.VERSION_CODES.HONEYCOMB) {
        child.setActivated(mCheckStates.get(position));
    }
}

ok,当我们设置的模式不是默认模式时,如果我们的view实现了Checkable,这个child就调用setChecked这个方法,这个方法又是个抽象方法,我们可以实现它做我们想做的事情。这就是我们为什么实现Checkable了。

好了,知道这些之后,我在说说整个流程:

首先listview.setChoiceMode:

public void setChoiceMode(int choiceMode) {
    mChoiceMode = choiceMode;
    if (mChoiceActionMode != null) {
        mChoiceActionMode.finish();
        mChoiceActionMode = null;
    }
    if (mChoiceMode != CHOICE_MODE_NONE) {
        if (mCheckStates == null) {
            mCheckStates = new SparseBooleanArray(0);
        }
        if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
            mCheckedIdStates = new LongSparseArray<Integer>(0);
        }
        // Modal multi-choice mode only has choices when the mode is active. Clear them.
        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
            clearChoices();
            setLongClickable(true);
        }
    }
}

调用这个方法时,这里面有两个重要的变量:1.mCheckStates 2.mCheckIdStates,若不懂这两个变量的类型,暂且把它们当成两个map,一个是Map<Integer,Boolean>类型,一个是Map<Long,Intger>类型,mCheckStates是用来标示哪些位置是选中的,mCheckIdStates是用来标示那些选中位置的viewId是多少。对于不懂SparseBooleanArray和LongSparseArray的可以参考我的一篇博客:http://my.oschina.net/gef/blog/600698?fromerr=MP2ZlkD6

然后当listview加载完成,我们点击某一项时,会执行:

public boolean performItemClick(View view, int position, long id) {...}

方法,其中有一段代码:

if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
        (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
    boolean checked = !mCheckStates.get(position, false);
    mCheckStates.put(position, checked);
    if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
        if (checked) {
            mCheckedIdStates.put(mAdapter.getItemId(position), position);
        } else {
            mCheckedIdStates.delete(mAdapter.getItemId(position));
        }
    }
    if (checked) {
        mCheckedItemCount++;
    } else {
        mCheckedItemCount--;
    }
    if (mChoiceActionMode != null) {
        mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
                position, id, checked);
        dispatchItemClick = false;
    }
    checkedStateChanged = true;
} else if (mChoiceMode == CHOICE_MODE_SINGLE) {
    boolean checked = !mCheckStates.get(position, false);
    if (checked) {
        mCheckStates.clear();
        mCheckStates.put(position, true);
        if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
            mCheckedIdStates.clear();
            mCheckedIdStates.put(mAdapter.getItemId(position), position);
        }
        mCheckedItemCount = 1;
    } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
        mCheckedItemCount = 0;
    }
    checkedStateChanged = true;
}

意思就是当你点击某一项的时候,根据你设置的ChoiceMode,那两个重要的变量mCheckStates 和mCheckIdStates会添加相应的值。同时也会回调setUpChild方法,然后根据:

if (child instanceof Checkable) {
        ((Checkable) child).setChecked(mCheckStates.get(position));
    }

展示setCheckable方法里面的内容。

OK,来个例子:

Activity中:

public class ListViewSingleChoiceActivity extends AppCompatActivity{
    private ListView listview;
    private ArrayList<String> arrayList = new ArrayList<>();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singlechoice);
        listview = (ListView) findViewById(R.id.listview);
        listview.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        for (int i = 0;i < 10;i++){
            arrayList.add("hahahahahahhahaha" + i);
        }
        listview.setAdapter(new MyAdapter(this,1,arrayList));
    }
    class MyAdapter extends ArrayAdapter<String>{
        public MyAdapter(Context context, int resource, List<String> objects) {
            super(context, resource, objects);
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            MyView myView = null;
            if (convertView == null){
                myView = new MyView(ListViewSingleChoiceActivity.this);
            }else{
                myView = (MyView) convertView;
            }
            myView.setText(getItem(position));
            return myView;
        }
    }
    class MyView extends FrameLayout implements Checkable{
        private TextView content;
        private CheckBox checkBox;
        public MyView(Context context) {
            super(context);
            View.inflate(context,R.layout.item_single_choice,this);
            content = (TextView) findViewById(R.id.content);
            checkBox = (CheckBox) findViewById(R.id.checkbox);
        }
        public void setText(String text) {
            content.setText(text);
        }
        @Override
        public void setChecked(boolean checked) {//重要方法
            checkBox.setChecked(checked);
        }
        @Override
        public boolean isChecked() {
            return checkBox.isChecked();
        }
        @Override
        public void toggle() {
            checkBox.toggle();
        }
    }
}

XML中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <CheckBox
        android:id="@+id/checkbox"
        android:text="hello"
        android:clickable="false"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/content"
        android:text="gefgef"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

效果图就不上传了,就是单选模式。

转载于:https://my.oschina.net/gef/blog/603230

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值