-
超簡單的單選和多選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; }
這樣,通過上面的方法,你就可以去實現各種自己自定義好布局的多選或單選列表了。