Android Selectable ListView 最佳實踐

前言

日常開發中,單選或多選列表是一種常見的需求。當然如果以“安卓 style”的交互理念,那么通常認為列表下長按進入“選擇模式”。感覺國內這種理念幾乎被忽略,產品經理(或者交互設計師)要求“與 IOS 保持一致”的情況也遍地都是。

在面對這樣的需求時,我們絕大多數開發者的解決方案一定是在 Adapter 下保持一個 HashMap<Object, Boolean>,用于保存數據集的選擇狀態,想的深入的也許會采用 SparseBooleanArray 等等優化的數據結構,或者干脆在 Model 類中加入 selected 的布爾屬性,然后通過 OnItemClickListener 設置選中狀態再 notifyDataChanged ,如果涉及到單選、多選、不可選模式的切換就更復雜,代碼也設計的慘不忍睹。但我這里要介紹的是一種更貼近原生 API 的解決方案。

ListView 的 choiceMode 屬性

之所以會想到要尋求更好的解決方案,是因為我發現 ListView 下的 choiceMode 很有意思。它似乎被我們遺忘了,是不是 SDK 本身就已經處理了這種的需求。
choiceMode 的值可以有以下幾個,在 ListView 下可以找到:

  • CHOICE_MODE_NONE
  • CHOICE_MODE_SINGLE
  • CHOICE_MODE_MULTIPLE
  • CHOICE_MODE_MULTIPLE_MODAL

除了最后一個,其他的幾個值得意思都是顯然易見的,MODAL 用于與 ActionBar 響應交互(?待驗證)。

可以在 XML 中配置:

android:choiceMode="singleChoice"

也可以代碼設置:

listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

讓開發更簡便

我們要做的只要以下幾步:

  • item 的根布局采用 CheckableRelativeLayout,顧名思義,考慮到 RelativeLayout 的全能,這里就只放相對布局了,這個類要繼承 RelativeLayout,實現 Checkable 接口,代碼會在文章最后呈現

  • 調用 ListView#setItemChecked 方法初始化選擇的狀態

  • 在 Adapter#getView 方法中加入

    final ListView lv = (ListView) parent;
    holder.rootLayout.setChecked(lv.isItemChecked(position));
    
  • 調用 ListView#getCheckedItemPositions 獲取被選中的位置,ListView#getCheckedItemIds 獲取被選中的數據 id,對于 SparseBooleanArray 的遍歷方法如下:

    int position = checkedItems.keyAt(i);
    boolean isChecked = checkedItems.valueAt(i);
    
  • 然后別忘了布局中每個控件設置:

    <Xxx
      ...
      android:focusable="false" />
    

CheckableRelativeLayout 源碼

代碼來源
https://github.com/marvinlabs/android-tutorials-custom-selectable-listview
侵權即刪

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;
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,523評論 25 708
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,569評論 2 45
  • 一、制作有單選item的ListView 主要有兩點: 設置ListView 的選擇模式為單選模式AbsListV...
    dayang閱讀 3,115評論 -1 12
  • 感冒是一種洶涌襲來的病毒,瓦解了我為數不多的意志,一個黑夜兼之白天都來的昏昏沉沉,睡夢中持續不斷的疼痛扼住了我咽喉...
    落辰墜夢閱讀 318評論 0 0
  • 寸光幸喜識安貧,照向丹青總是春。 幾角諍言言莫逆,十年心曲曲還真。 折腰不對輕浮子,俯首惟交厚道人。 夜雨君歸尋凈...
    銓齋閱讀 2,339評論 22 41