有很多人在使用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>
效果图就不上传了,就是单选模式。