我們知道在一般的列表檢視(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型別的強轉.
所以還是要多看官方成熟的庫, 他們的設計和實現都是經過千錘百煉, 對學習非常有益處.