你還在爲列表[單選]、[多選]寫重複的邏輯嗎

一 瞎扯淡

項目中常常性會碰到列表的單選、多選,實現起來好像也不難,可是最近項目有好多個須要單選/多選的頁面,看到設計稿的一瞬間,腦子靈光一閃,爲啥不把這些簡單而又繁瑣的邏輯給封裝起來呢(懶癌發做)?git

因而就有了下面的小東西(開源庫)...github

二 功能

1.列表單選

  • 普通單選
  • 預選中
  • 不能取消
  • 多類型
  • 多列表

2.列表多選

  • 普通多選
  • 預選中
  • 全選/取消全選
  • 多類型
  • 多列表

3.優勢

  • 簡單易用
  • 低耦合
  • 不用爲Bean添加額外字段
  • 沒有調用 Adapter.notifyItemChange方法,因此不會有閃屏Bug

三 效果

廢話少說,先上圖:bash

1.單選

單選

2.多選

四 使用

1.配置

首先在你的工程根目錄的 gradle 文件下添加如下配置:markdown

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
複製代碼

接着就能夠在你的moudle,通常是 app 的 gradle 文件下添加依賴:app

implementation 'com.github.gminibird:CheckHelper:1.0'
複製代碼

最後那個1.0是版本號,能夠上 GitHub 上看最新的,而後就能夠愉快的玩耍啦。maven

2.使用

  • 建立CheckHelper實例
    SingleCheckHelper mCheckHelper = new SingleCheckHelper();
    //or
    MultiCheckHelper mCheckHelper = new MultiCheckHelper();
    複製代碼
  • 註冊選擇器
    mCheckHelper.register(String.class, new CheckHelper.Checker<String, LwViewHolder>() 
    @Override
    public void check(String s, LwViewHolder holder) {
        //選中狀態
        holder.itemView.setBackgroundColor(0xFF73E0E4); //藍色
        holder.setChecked(R.id.checkbox, true);
    }
    @Override
    public void unCheck(String s, LwViewHolder holder) {
        //非選中狀態
        holder.itemView.setBackgroundColor(0xFFFFFFFF);  //白色
        holder.setChecked(R.id.checkbox, false);
    }
    });
    複製代碼
  • 綁定到Adapter中
    @Override
    protected void onBind(@NonNull LwViewHolder holder, @NonNull String item) {
        //這裏用了本身封裝的Adapter,至關於onBindViewHolder方法
        mCheckHelper.bind(item, holder, holder.itemView);
    }
    複製代碼

而後,而後就完成了。。。運行就能夠看到想要的效果,選中的數據能夠調用相應CheckHelper實例的getXXX()獲取。ide

五 實現原理

總的原理其實很簡單,就是對應的 CheckHelper 實現類內部維護一些選中的數據。下面細說下具體實現:oop

1. 模板類 CheckHelper

CheckHelper 是目前兩個實現類的基類,提供了一些基礎的公共功能,好比設置監聽器,註冊選擇器等,其實就是一個模板模式,把公共部分以及執行順序都定了,而後交由子類完成具體的數據增改。gradle

裏面有兩個重要的方法,一個是bind(),另外一個是 select(),分別對應了onBindViewHolder以及onClick(點擊)方法:this

public final void bind(final Object d, final RecyclerView.ViewHolder v, View clickedView) {
    if (clickedView == null) {
        throw new NullPointerException("ClickedView can not be null!");
    }
    bind(d, v, isChecked(d, v)); //註釋1
    clickedView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            select(d, v);  //註釋2
        }
    });
}
複製代碼

咱們在onBindViewHolder()方法中綁定的 bind() 方法最終會調用這個方法,先看註釋1註釋1裏的bind方法裏面作了一系列的監聽器的回調:

public void bind(Object d, RecyclerView.ViewHolder v, boolean toCheck) {
    Checker checker = mCheckerMap.get(d.getClass());
    if (checker != null) {
        if (toCheck) {
            checker.check(d, v);
        } else {
            checker.unCheck(d, v);
        }
    }
    OnCheckListener checkListener = mCheckListenerMap.get(d.getClass());
    if (checkListener != null) {
        checkListener.onCheck(d, v, toCheck);
    }
    onBindListener bindListener = mOnBindListenerMap.get(d.getClass());
    if (bindListener != null) {
        bindListener.onBind(d, v, toCheck);
    }
}
複製代碼

其中包括 Checker ,也就是咱們註冊爲非選中和選中狀態設置的選擇器會被調用,而後就是 OnCheckListener 以及 onBindListener ,這兩個咱們均可以add 進去。

再返回看最上面的bind() 方法裏面的註釋2,調用了一系列回調後,而後便爲 item 的某個 view 設置一個監聽,點擊後就會執行 select方法,select方法裏面也是作了一系列回調,基本和上面的方法一致。因此子類只要重寫這兩個方法,並實現相應的數據操做便可。

2. SingleCheckHelper 單選

單選其實不難,但有一點比較難搞:

選中後怎麼把上一次選中給取消?

若是按照平時的寫法,咱們在Bean裏面增長一個 isChecked 字段,而後選擇這個時將當前選中置爲 true,接着將上一個選擇的置爲 false 最後調用 adapter 刷新一下就能夠了。

可是這裏沒有依賴具體的Bean,也沒有新增字段,那怎麼弄呢,後來想到了一個小技巧,我沒有依賴Bean,可是能夠在 ViewHolder 裏面存儲信息,因此就有下面的代碼:

@Override
public void select(Object d, RecyclerView.ViewHolder v, boolean toCheck) {
    if (toCheck) {
        unCheckPre(d);  //註釋1
        setTag(v);    //註釋2
        this.v = v;
        this.d = d;
    } else {
        if (!canCancel){
            return;
        }
        clearTag(v);  //註釋3
        this.d = null;
        this.v = null;
    }
    super.select(d, v, toCheck);
}
複製代碼

邏輯也很簡單,若是是選中狀態,並且存有tag,那麼說明是以前已經有選中的,就將以前選中的tag清除,而後再把當前的選中設置一個tag,若是是取消選中,那麼就將tag給清除掉:

private void unCheckPre(Object d) {
    if (this.d == null || this.d.equals(d)) {
        return;
    }
    if (this.d != null && this.v != null && this.v.itemView.getTag(TAG) != null) {
        //當上一個選中存在而且可見時置爲非選
        bind(d, v, false);
    }
}

private void setTag(RecyclerView.ViewHolder v) {
    if (v != null) {
        v.itemView.setTag(TAG, TAG_VALUE);
    }
}
複製代碼

接着就回調父類的一系列回調接口了。

3.MultiCheckHelper 多選

多選類主要是靠一個 Map 來維護選中的數據,每一個數據類型對應一個 Set ,選中和非選中就調用相應的方法更新數據,沒有很大難點。

protected HashMap<Class, Set<?>> mMap;

@SuppressWarnings("unchecked")
public void add(Object d) {
    Set<Object> set = (Set<Object>) mMap.get(d.getClass());
    if (set == null) {
        set = new HashSet<>();
        mMap.put(d.getClass(), set);
    }
    set.add(d);
}
public void remove(Object d) {
    Set set = mMap.get(d.getClass());
    if (set != null) {
        set.remove(d);
        if (set.size() == 0) {
            mMap.remove(d.getClass());
        }
    }
}
複製代碼

因爲做者水平有限,若是有更好的方法歡迎探討。

written by gminibird

源碼戳上面 ^^^

做者介紹

  • 曾榮基:廣州蘆葦科技 APP 團隊 Android 開發工程師

內推信息

  • 咱們正在招募小夥伴,有興趣的小夥伴能夠把簡歷發到 app@talkmoney.cn,備註:來自掘金社區
  • 詳情能夠戳這裏--> 廣州蘆葦信息科技
相關文章
相關標籤/搜索