前言
日常開發(fā)中,單選或多選列表是一種常見的需求。當然如果以“安卓 style”的交互理念,那么通常認為列表下長按進入“選擇模式”。感覺國內(nèi)這種理念幾乎被忽略,產(chǎn)品經(jīng)理(或者交互設(shè)計師)要求“與 IOS 保持一致”的情況也遍地都是。
在面對這樣的需求時,我們絕大多數(shù)開發(fā)者的解決方案一定是在 Adapter 下保持一個 HashMap<Object, Boolean>,用于保存數(shù)據(jù)集的選擇狀態(tài),想的深入的也許會采用 SparseBooleanArray 等等優(yōu)化的數(shù)據(jù)結(jié)構(gòu),或者干脆在 Model 類中加入 selected 的布爾屬性,然后通過 OnItemClickListener 設(shè)置選中狀態(tài)再 notifyDataChanged ,如果涉及到單選、多選、不可選模式的切換就更復雜,代碼也設(shè)計的慘不忍睹。但我這里要介紹的是一種更貼近原生 API 的解決方案。
ListView 的 choiceMode 屬性
之所以會想到要尋求更好的解決方案,是因為我發(fā)現(xiàn) ListView 下的 choiceMode 很有意思。它似乎被我們遺忘了,是不是 SDK 本身就已經(jīng)處理了這種的需求。
choiceMode 的值可以有以下幾個,在 ListView 下可以找到:
- CHOICE_MODE_NONE
- CHOICE_MODE_SINGLE
- CHOICE_MODE_MULTIPLE
- CHOICE_MODE_MULTIPLE_MODAL
除了最后一個,其他的幾個值得意思都是顯然易見的,MODAL 用于與 ActionBar 響應交互(?待驗證)。
可以在 XML 中配置:
android:choiceMode="singleChoice"
也可以代碼設(shè)置:
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
讓開發(fā)更簡便
我們要做的只要以下幾步:
item 的根布局采用 CheckableRelativeLayout,顧名思義,考慮到 RelativeLayout 的全能,這里就只放相對布局了,這個類要繼承 RelativeLayout,實現(xiàn) Checkable 接口,代碼會在文章最后呈現(xiàn)
調(diào)用 ListView#setItemChecked 方法初始化選擇的狀態(tài)
-
在 Adapter#getView 方法中加入
final ListView lv = (ListView) parent; holder.rootLayout.setChecked(lv.isItemChecked(position));
-
調(diào)用 ListView#getCheckedItemPositions 獲取被選中的位置,ListView#getCheckedItemIds 獲取被選中的數(shù)據(jù) id,對于 SparseBooleanArray 的遍歷方法如下:
int position = checkedItems.keyAt(i); boolean isChecked = checkedItems.valueAt(i);
-
然后別忘了布局中每個控件設(shè)置:
<Xxx ... android:focusable="false" />
CheckableRelativeLayout 源碼
代碼來源
https://github.com/marvinlabs/android-tutorials-custom-selectable-listview
侵權(quán)即刪
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import android.widget.RelativeLayout;
/**
* Extension of a relative layout to provide a checkable behaviour
*
* @author marvinlabs
*/
public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
/**
* Interface definition for a callback to be invoked when the checked state of a CheckableRelativeLayout changed.
*/
public static interface OnCheckedChangeListener {
public void onCheckedChanged(CheckableRelativeLayout layout, boolean isChecked);
}
public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialise(attrs);
}
public CheckableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialise(attrs);
}
public CheckableRelativeLayout(Context context, int checkableId) {
super(context);
initialise(null);
}
/*
* @see android.widget.Checkable#isChecked()
*/
public boolean isChecked() {
return isChecked;
}
/*
* @see android.widget.Checkable#setChecked(boolean)
*/
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
for (Checkable c : checkableViews) {
c.setChecked(isChecked);
}
if (onCheckedChangeListener != null) {
onCheckedChangeListener.onCheckedChanged(this, isChecked);
}
}
/*
* @see android.widget.Checkable#toggle()
*/
public void toggle() {
this.isChecked = !this.isChecked;
for (Checkable c : checkableViews) {
c.toggle();
}
}
public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
this.onCheckedChangeListener = onCheckedChangeListener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int childCount = this.getChildCount();
for (int i = 0; i < childCount; ++i) {
findCheckableChildren(this.getChildAt(i));
}
}
/**
* Read the custom XML attributes
*/
private void initialise(AttributeSet attrs) {
this.isChecked = false;
this.checkableViews = new ArrayList<Checkable>(5);
}
/**
* Add to our checkable list all the children of the view that implement the interface Checkable
*/
private void findCheckableChildren(View v) {
if (v instanceof Checkable) {
this.checkableViews.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));
}
}
}
private boolean isChecked;
private List<Checkable> checkableViews;
private OnCheckedChangeListener onCheckedChangeListener;
}