ListView多选和单选模式重新整理
-
超简单的单选和多选ListView
在开发过程中,我们经常会使用ListView去呈现列表数据,比如商品列表,通话记录,联系人列表等等,在一些情况下,我们还需要去选择其中的一些列表数据进行编辑。以前,我在项目开发中,都是在自定义的Adapter中去维护一个SparseBooleanArray变量来保存当前ListView中已经被选中的项,然后在自定义Adapter的getView()和ListView的setOnItemClickListener()方法中去实时更新SparseBooleanArray变量,从而当用户选择提交数据的时候,直接遍历SparseBooleanArray中的值就可以了,这种做法,虽然也能实现功能,但是无疑增加了代码开销。
今天,在看文档的时候,发现了一个更好的解决方案(很多人已经用过了吧):使用ListView的choiceMode,官方文档见如下:
根据上面的文档说明,可以知道,android:choiceMode有以下几个值:默认(不设置android:choiceMode属性,即不支持单选或多选),singleChoice(单选),multipleChoice(多选),mutipleChoiceModal(特殊多选模式,可以通过设置MultiChoiceModeListener进行监听选择模式,类似ActionBar的ActionMode)。在XML中给ListView设置了android:choiceMode属性一个值后,我们还需要给ListView一个适配器,这里我们使用默认的ArrayAdapter:
//如果是单选模式,则可以使用 android.R.layout.simple_list_item_single_choice ArrayAdapter<String> myAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice, mDatas); mListView.setAdapter(myAdapter);
这样我们就已经完成了ListView的多选或单选实现。
另外一个问题是:我们怎样才能获取当前ListView中被选中的那些项呢?
我们其实可以通过ListView的isItemChecked(int position)方法判断一项是否被选中,或直接使用ListView.getCheckedItemPositions()来获取所有选中的项,这个方法返回一个SparseBooleanArray对象,遍历它就可以获取所有选中的项。
-
自定义ListView的多选和单选项布局
对于一些简单的列表,上面的方法可能已经能够满足需求。其实,上面的列表项只显示了一个标题和一个复选框,但在实际开发中,UE或产品经理可能要求我们去实现的列表远比上面的列表复杂得多,所以往往就需要使用自定义的Adapter来填充ListView。
但是,如果我们使用自定义的Adapter来填充ListView,那怎么让我们自定义的Checkbox能够无缝衔接ListView的选择状态呢?
一种普遍的做法是在重写自定义Adapter的getView()时,先通过convertView.findViewById()获取到Checkbox后,通过mList.isItemChecked(int position)判断当前position的状态后,再去更新Checkbox的选择状态。
这里,我介绍的是另外一种方法。
首先,我们先来看下为什么我们使用android.R.layout.simple_list_item_multiple_choice布局来填充ArrayAdapter时,不需要我们自己去维护CheckBox的选择状态?
查看ListView的源码,在ListView的setupChild方法中,有下面的一段代码:
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)); } }
即如果ListView的child(从自定义的Adapter的getView()方法中返回的View)实现了Checkable接口,那么当listView的项选择状态改变时,listView也会去同步更新这个child的状态(android 3.1或3.1以上平台,会触发setActivated方法),其实simple_list_item_multiple_choice.xml中只有一个CheckedTextView
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="?android:attr/listPreferredItemHeightSmall" android:textAppearance="?android:attr/textAppearanceListItemSmall" android:gravity="center_vertical" android:checkMark="?android:attr/listChoiceIndicatorMultiple" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" />
而CheckedTextView 是实现了Checkable接口的,所以当我们使用simple_list_item_multiple_choic.xml布局作为Adapter的getView()的返回值时,是不需要我们额外去关心Checkbox的状态问题。
通过上面的分析,我们自定义一个View时,只需要实现了Checkable接口,那么就不用我们在getView中去额外维护选中状态了。如果android3.1或android3.1以上的平台,我们还可以重写setActivated方法来更新我们的选中状态。相关示例代码如下:
package com.shaoxiong.li.marvel.myapplication; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.Checkable; import android.widget.LinearLayout; import android.widget.TextView; /** * Created by lishaoxiong on 16-2-22. */ public class CustomCheckTextView extends LinearLayout implements Checkable { private TextView titleView; private CheckBox mCheckBox; public CustomCheckTextView(Context context) { this(context, null); } public CustomCheckTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomCheckTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater mLayoutInflater = LayoutInflater.from(context); //将加载出来的View添加到当前View层级中去。 //有两种方案,一种是加载布局时将rootView传进去,或直接使用addView添加进去 //View v = mLayoutInflater.inflate(R.layout.layout_custom_ctv, null); View v = mLayoutInflater.inflate(R.layout.layout_custom_ctv, this, true); titleView = (TextView)v.findViewById(R.id.headListView_item_text); mCheckBox = (CheckBox)v.findViewById(R.id.headListView_item_cb); //this.addView(v); } @Override public void setChecked(boolean checked) { mCheckBox.setChecked(checked); } @Override public boolean isChecked() { return mCheckBox.isChecked(); } @Override public void toggle() { mCheckBox.toggle(); } public void setTitle(String title) { titleView.setText(title); } @Override public void setActivated(boolean activated) { super.setActivated(activated); } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:padding="20dp"> <CheckBox android:id="@+id/headListView_item_cb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="false" android:clickable="false" android:focusableInTouchMode="false"/> <TextView android:id="@+id/headListView_item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp"/> </LinearLayout>
public class MyAdapter extends BaseAdapter { private Context mContext; private ArrayList<String> dataList; public MyAdapter(Context context, ArrayList<String> dataList) { this.mContext = context; this.dataList= dataList; } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if(convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_headlistview, null); viewHolder = new ViewHolder(); //viewHolder.mTextView = (TextView)convertView.findViewById(R.id.headListView_item_text); // viewHolder.checkedTv = (CheckedTextView)convertView.findViewById(R.id.item_checked_tv); viewHolder.customCheckTextView = (CustomCheckTextView)convertView; convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder)convertView.getTag(); } // viewHolder.checkedTv.setText(dataList.get(position)); // viewHolder.mTextView.setText(dataList.get(position)); viewHolder.customCheckTextView.setTitle(dataList.get(position)); return convertView; } } static class ViewHolder { // TextView mTextView; // CheckedTextView checkedTv; CustomCheckTextView customCheckTextView; }
这样,通过上面的方法,你就可以去实现各种自己自定义好布局的多选或单选列表了。