一勞永逸——RecyclerView無型別強轉的通用ViewHolder

林鹿發表於2019-06-02

我們知道在一般的列表檢視(recyclerView)中繫結不同型別的列表項子檢視是通過各種型別的ViewHolder(比如recyclerView.ViewHolder). 不同資料對不同檢視控制元件的操作是以實現各種ViewHolder子類的方式實現的.

能不能只用一種型別的檢視來涵蓋所有的ViewHolder型別? 聽起來有些不可思議, 每種ViewHolder需要繫結的控制元件千差萬別, 怎麼抽象這些控制元件呢? 但實際上是可以實現的.

在support.v7.preference庫中作者就用了一種方式實現這種效果:

public class PreferenceViewHolder extends RecyclerView.ViewHolder {
    private final SparseArray<View> mCachedViews = new SparseArray<>(4);

    public View findViewById(@IdRes int id) {
        final View cachedView = mCachedViews.get(id);
        if (cachedView != null) {
            return cachedView;
        } else {
            final View v = itemView.findViewById(id);
            if (v != null) {
                mCachedViews.put(id, v);
            }
            return v;
        }
    }
}
複製程式碼

這樣外部只需通過findViewById來找到各種各樣的控制元件例項來進行資料繫結即可, 但是宣告的ViewHolder卻只需一種! 仔細想想這種通過SparseArray持有的方式其實非常巧妙, 真正將ViewHolder作為各種檢視的持有者(Holder)不用再區分型別, 可謂實至名歸.

稍加改造就可以和新API的findViewById風格完全保持一致(我們姑且叫做ItemViewHolder, 抽象所有列表檢視子檢視):

public class ItemViewHolder extends RecyclerView.ViewHolder  {
    private final SparseArrayCompat<View> mCached = new SparseArrayCompat<>(10);

    public ItemViewHolder(View itemView) {
        super(itemView);
    }

    public <T extends View> T findViewById(@IdRes int resId) {
        int pos = mCached.indexOfKey(resId);
        View v;
        if (pos < 0) {
            v = itemView.findViewById(resId);
            mCached.put(resId, v);
        } else {
            v = mCached.valueAt(pos);
        }
        @SuppressWarnings("unchecked")
        T t = (T) v;
        return t;
    }
}
複製程式碼

其實RecyclerView.ViewHolder本身就應該設計成這種方式, 並且宣告成final強制移除各種Viewholder型別的強轉.

所以還是要多看官方成熟的庫, 他們的設計和實現都是經過千錘百煉, 對學習非常有益處.

相關文章