Android RecyclerView 數(shù)據(jù)綁定實(shí)例代碼
前言
在上一個(gè)項(xiàng)目里有很多很多很多很多的RecyclerView,然后我需要寫很多很多很多很多的Adapter和Viewholder——多倒沒問題,但是里面有很多重復(fù)的代碼這就不能忍了!每一個(gè)Adapter和ViewHolder其實(shí)做的事情非常的像:視圖綁定,數(shù)據(jù)綁定,點(diǎn)擊事件分發(fā)。還有啥?既然它們做的事情都一樣,為啥我們還要傻傻的繼續(xù)寫著重復(fù)的代碼?
正文
BaseAdapter
通常我們要?jiǎng)?chuàng)建一個(gè)RecyclerView.Adapter是怎么做的?
- 接收一個(gè)數(shù)據(jù)列表
- 重寫getItemCount()方法,確定Item的個(gè)數(shù)
- 重寫onCreateViewHolder()方法,綁定Layout,新建一個(gè)我們自己寫的RecyclerView.ViewHolder
- 重寫onBindViewHolder()方法,進(jìn)行數(shù)據(jù)和視圖綁定
- 由于RecyclerView沒有寫點(diǎn)擊事件,把點(diǎn)擊事件分發(fā)出去
基本上就是這個(gè)套路,或者再加一個(gè)refreshData()的方法——傳新的數(shù)據(jù)進(jìn)來然后notifyDataSetChanged()。基于這些點(diǎn),我寫了一個(gè)BaseAdapter基類:
/**
* Adapter基類.
* 適用于只有單個(gè)Item的RecyclerView.
*
* Created by lypeer on 16-5-24.
*/
public abstract class BaseAdapter<V> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* 裝載了每個(gè)Item的Value的列表
*/
private List<V> mValueList;
/**
* 我寫的一個(gè)接口,通過回調(diào)分發(fā)點(diǎn)擊事件
*/
private OnItemClickListener<V> mOnItemClickListener;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return createViewHolder(parent.getContext(), parent);
}
@Override
@SuppressWarnings("unchecked")//一定會(huì)是BaseViewHolder的子類,因?yàn)閏reateViewHolder()的返回值
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//BaseViewHolder是我抽象出來的RecyclerView.ViewHolder的基類,下面會(huì)有詳細(xì)講解
((BaseViewHolder) holder).setData(mValueList.get(position), position, mOnItemClickListener);
}
/**
* 設(shè)置每個(gè)Item的點(diǎn)擊事件
* @param listener
*/
public void setOnClickListener(OnItemClickListener<V> listener) {
this.mOnItemClickListener = listener;
}
/**
* 刷新數(shù)據(jù)
* @param valueList 新的數(shù)據(jù)列表
*/
public void refreshData(List<V> valueList) {
this.mValueList = valueList;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return mValueList == null ? 0 : mValueList.size();
}
/**
* 生成ViewHolder
* @param context
* @param parent
* @return
*/
protected abstract BaseViewHolder createViewHolder(Context context, ViewGroup parent);
}
它的子類在繼承它的時(shí)候需要指定泛型的具體類型,因?yàn)椴煌腎tem也許其數(shù)據(jù)類型并不一樣,這樣就可以適應(yīng)更多的Item。另外,其中提到了一個(gè)接口OnItemClickListener,這個(gè)接口很簡單:
/**
* 點(diǎn)擊事件的接口
* Created by lypeer on 16-5-24.
*/
public interface OnItemClickListener<V> {
/**
* 當(dāng)item被點(diǎn)擊的時(shí)候進(jìn)行事件分發(fā)
*
* @param itemValue 點(diǎn)擊的item傳遞的值
* @param viewID 點(diǎn)擊控件的id
* @param position 被點(diǎn)擊的item的位置
*/
void onItemClick(V itemValue, int viewID, int position);
}
在使用它的時(shí)候同樣需要使用泛型——原因和上面一樣。
通過上面的BaseAdapter,我們把很多的共有操作都封裝在了基類里面,而它的子類只需要根據(jù)需要新建不同的ViewHolder就行了——當(dāng)然,這個(gè)viewHolder必須繼承自BaseViewHolder,而BaseViewHolder是什么下面會(huì)有詳細(xì)講解。接下來是一個(gè)例子,假設(shè)我們現(xiàn)在在一個(gè)界面要有一個(gè)RecyclerView,它的每個(gè)Item的數(shù)據(jù)是一個(gè)String值,那么怎么使用我們的BaseAdapter簡化開發(fā)過程呢?
public class SampleAdapter extends BaseAdapter<String> {
@Override
protected BaseViewHolder createViewHolder(Context context, ViewGroup parent) {
//SampleViewHolder繼承自BaseViewHolder
return new SampleViewHolder(context, parent);
}
}
是的,你沒有看錯(cuò)!就只有這么幾行代碼!5秒完成!驚喜么?!
你只需要新建一個(gè)SampleAdapter繼承自BaseAdapter,然后指定其泛型為String,再return new SampleViewHolder(context, parent) , 就完成了整個(gè)操作。
但是,有些讀者也許會(huì)有疑惑:也許我們需要在SampleViewHolder里面做很多的操作呢?那豈不是只是把代碼轉(zhuǎn)換了一個(gè)地方而已,實(shí)質(zhì)上并沒有什么優(yōu)化之處。
然而并不是。
BaseViewHolder
我一直認(rèn)為,將ViewHolder寫在Adapter里面是挺不明智的一個(gè)做法。這樣的話Adapter這個(gè)類的職責(zé)太重了,它做得事情太多了,從數(shù)據(jù)接收到界面綁定,從控件初始化到點(diǎn)擊事件分發(fā),它簡直什么都做完了。而這樣是很不好的。很輕易的,一個(gè)比較復(fù)雜的RecyclerView的Adapter的代碼就能達(dá)到成百上千行,這很可怕,這意味著這個(gè)類將變得冗雜且難以維護(hù)。那么怎么避免這種情況發(fā)生呢?我選擇了將ViewHolder分離,同時(shí)加重ViewHolder的職責(zé),使它們能比較均衡。
直接看BaseViewHolder的代碼:
/**
* ViewHolder基類
*
* Created by lypeer on 16-5-27.
*/
public abstract class BaseViewHolder<V> extends RecyclerView.ViewHolder {
public BaseViewHolder(Context context, ViewGroup root, int layoutRes) {
super(LayoutInflater.from(context).inflate(layoutRes, root, false));
//這里使用了ButterKnife來進(jìn)行控件的綁定
ButterKnife.bind(this, itemView);
}
/**
* 方便其子類進(jìn)行一些需要Context的操作.
*
* @return 調(diào)用者的Context
*/
public Context getContext() {
return itemView.getContext();
}
/**
* 抽象方法,綁定數(shù)據(jù).
* 讓子類自行對數(shù)據(jù)和view進(jìn)行綁定
*
* @param itemValue Item的數(shù)據(jù)
* @param position 當(dāng)前item的position
* @param listener 點(diǎn)擊事件監(jiān)聽者
*/
protected abstract void bindData(V itemValue, int position, OnItemClickListener listener);
/**
* 用于傳遞數(shù)據(jù)和信息
*
* @param itemValue
* @param position
* @param listener
*/
public void setData(V itemValue, int position, OnItemClickListener listener) {
bindData(itemValue, position, listener);
}
}
BaseViewHolder同樣采用了泛型,以適應(yīng)不同的數(shù)據(jù)類型。同時(shí),我在BaseViewHolder里面使用了ButterKnife來簡化代碼,從此再也不用反反復(fù)復(fù)的findViewById了。
另外,大家可以看到BaseViewHolder里面的構(gòu)造方法中傳入了三個(gè)參數(shù),但是在上面BaseAdapter的例子里面SampleViewHolder的構(gòu)造方法我們卻只傳入了它的前兩個(gè)構(gòu)造參數(shù),而第三個(gè)參數(shù)layoutRes并沒有傳進(jìn)去,這是怎么回事呢?是上面寫錯(cuò)了么?當(dāng)然不是。BaseViewHolder的構(gòu)造方法中有三個(gè)傳參是因?yàn)樗枰齻€(gè)傳參,而它的子類只有兩個(gè)傳參是因?yàn)樗荒苡袃蓚€(gè)傳參。_BaseViewHolder必須要滿足它的super構(gòu)造,所以必須要有那三個(gè)參數(shù),而它的子類如果那三個(gè)參數(shù)都是由外界傳進(jìn)來的,那么它怎么進(jìn)行針對那個(gè)布局進(jìn)行特異化的操作?它必須在類里面顯式的指定Layout ID_——這其實(shí)是我很想優(yōu)化的一個(gè)地方,因?yàn)檫@樣的話子類繼承BaseViewHolder之后還要修改它的構(gòu)造方法,這是比較讓人不省心的,但是目前還沒有想到什么好點(diǎn)子來優(yōu)雅地優(yōu)化它。
BaseViewHolder里面有兩個(gè)看起來很像的方法:setData()和bindData(),然而實(shí)際上除了傳參相同,它們其他方面根本完全不一樣。setData()方法是一個(gè)public的方法,可以由BaseViewHolder的子類的對象調(diào)用,其作用是從外部傳入數(shù)據(jù),以供ViewHolder所hold的那個(gè)view初始化,它可以說是一座傳輸信息的橋梁;而getData()是一個(gè)抽象的方法,它的具體實(shí)現(xiàn)在每一個(gè)BaseViewHolder的子類中,這些子類在這個(gè)方法里面進(jìn)行控件的綁定和初始化,以及對控件的點(diǎn)擊事件的處理等等。
說到點(diǎn)擊事件的處理,它的子類應(yīng)該怎么完成這件事呢?通過OnItemClickListener。我們可以借助于OnItemClickListener的接口回調(diào)來將需要處理的點(diǎn)擊事件傳遞到外界,然后由外界進(jìn)行處理。那么如果有多個(gè)控件的點(diǎn)擊事件需要處理怎么辦?不用擔(dān)心,因?yàn)樵贠nItemClickListener的onClick方法中你需要傳入點(diǎn)擊的控件的id,這樣一來就可以在外界進(jìn)行對傳入id的判斷,從而針對不同的id執(zhí)行不同的點(diǎn)擊事件了。
接下來通過一個(gè)例子來看下具體怎么用,就是上面的那個(gè)SampleViewHolder吧:
public class SampleViewHolder extends BaseViewHolder<String> {
//一個(gè)普通的可點(diǎn)擊的TextView
@Bind(R.id.is_tv_content)
TextView mIsTvContent;
public SampleViewHolder(Context context, ViewGroup root) {
//修改了構(gòu)造方法,在這里顯式指定Layout ID
super(context, root, R.layout.item_sample);
}
@Override
protected void bindData(final String itemValue, final int position, final OnItemClickListener listener) {
//在這里完成控件的初始化,將其與數(shù)據(jù)綁定
if (itemValue != null) {
mIsTvContent.setText(itemValue);
}
//如果需要有點(diǎn)擊事件,就通過listener把它傳遞出去
mIsTvContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//如果外界沒有調(diào)用BaseAdapter.setOnClickListener(),
//listener就為null
if(listener == null){
return;
}
//listener不為null就將這個(gè)事件傳遞給外界處理
listener.onItemClick(itemValue , v.getId() , position);
}
});
}
}
同樣很簡單方便快捷清晰。但是還是有幾點(diǎn)值得注意的地方。首先,不能忘了修改構(gòu)造方法,顯式的在super里面指定Layout ID,不然下一步都沒法做,那就懵逼了。另外,在調(diào)用listener.onClick()方法的時(shí)候必須進(jìn)行l(wèi)istener的驗(yàn)空——因?yàn)閘istener真的有可能為空!這樣的話非常容易產(chǎn)生空異常導(dǎo)致程序崩潰。
賓果,就這樣,SampleViewHolder就完成了,同樣幾乎不費(fèi)吹灰之力。
結(jié)語
這篇博文的目的是分享一些我總結(jié)出來的東西,希望能讓大家的開發(fā)加速那么一點(diǎn)點(diǎn)。當(dāng)然,這里面也許還有我沒發(fā)現(xiàn)的bug什么的,如果大家在使用的過程中發(fā)現(xiàn)了問題請不要客氣,狠狠地砸給我吧!
最后還是再總結(jié)一下在有了BaseAdapter和BaseViewHolder的情況下怎么最快的搞定RecyclerView的那一套:
ViewHolder相關(guān)
新建 XXXViewHolder 繼承自BaseViewHolder,指定泛型類型(也就是Item中數(shù)據(jù)的數(shù)據(jù)類型)。
刪掉構(gòu)造方法中的layoutRes參數(shù),在super里面顯式指定Layout ID。
用ButterKnife綁定控件。
在bindData()方法中完成控件的初始化以及點(diǎn)擊事件的傳遞(別忘了listener的驗(yàn)空)
Adapter相關(guān)
新建 XXXAdapter 繼承自BaseAdapter,指定泛型類型(也就是Item中數(shù)據(jù)的數(shù)據(jù)類型)。
return new XXXViewHolder(context, parent);
外界相關(guān)
綁定RecyclerView,新建XXXAdapter。
調(diào)用 BaseAdapter.refreshData()方法傳入數(shù)據(jù)列表。
如果有對點(diǎn)擊事件處理的需求,則調(diào)用BaseAdapter.setOnClickListener()方法。
目前我只做了針對RecyclerView中單個(gè)的Item的BaseAdapter,但是BaseViewHolder使可以通用的,并且其在多Item下也可以大大的簡化Adapter的體積。
以上就是對Android RecyclerView 數(shù)據(jù)綁定的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料謝謝大家對本站的支持!
- Android DataBinding單向數(shù)據(jù)綁定深入探究
- 淺析Android企業(yè)級(jí)開發(fā)數(shù)據(jù)綁定技術(shù)
- Android Studio綁定下拉框數(shù)據(jù)詳解
- 詳解Android的MVVM框架 - 數(shù)據(jù)綁定
- Android Data Binding數(shù)據(jù)綁定詳解
- Android ListView數(shù)據(jù)綁定顯示的三種解決方法
- Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法
- Android數(shù)據(jù)雙向綁定原理實(shí)現(xiàn)和應(yīng)用場景
相關(guān)文章
基于Android 錯(cuò)誤信息捕獲發(fā)送至服務(wù)器的詳解
本篇文章是對Android中錯(cuò)誤信息捕獲發(fā)送服務(wù)器進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android 購物車加減功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 實(shí)現(xiàn)購物車加減功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android Studio去除界面默認(rèn)標(biāo)題欄的方法
這篇文章主要介紹了Android Studio去除界面默認(rèn)標(biāo)題欄的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2007-09-09
Android中Glide加載圓形圖片和圓角圖片實(shí)例代碼
本篇文章主要介紹了Android中Glide加載圓形圖片和圓角圖片實(shí)例代碼,具體一定的參考價(jià)值,有興趣的可以了解一下2017-05-05
Android實(shí)現(xiàn)讀取SD卡下所有TXT文件名并用listView顯示出來的方法
這篇文章主要介紹了Android實(shí)現(xiàn)讀取SD卡下所有TXT文件名并用listView顯示出來的方法,涉及Android針對SD卡的讀取及文件遍歷等相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
基于Android設(shè)計(jì)模式之--SDK源碼之策略模式的詳解
本篇文章介紹了,基于Android設(shè)計(jì)模式之--SDK源碼之策略模式的詳解。需要的朋友參考下2013-04-04
Android沉浸式狀態(tài)欄微技巧(帶你真正理解沉浸式模式)
因?yàn)锳ndroid官方從來沒有給出過沉浸式狀態(tài)欄這樣的命名,只有沉浸式模式(Immersive Mode)這種說法.下面通過本文給大家介紹Android沉浸式狀態(tài)欄微技巧,需要的朋友參考下2016-12-12

