編輯:關於Android編程
前言
在上一個項目裡有很多很多很多很多的RecyclerView,然後我需要寫很多很多很多很多的Adapter和Viewholder——多倒沒問題,但是裡面有很多重復的代碼這就不能忍了!每一個Adapter和ViewHolder其實做的事情非常的像:視圖綁定,數據綁定,點擊事件分發。還有啥?既然它們做的事情都一樣,為啥我們還要傻傻的繼續寫著重復的代碼?
正文
BaseAdapter
通常我們要創建一個RecyclerView.Adapter是怎麼做的?
基本上就是這個套路,或者再加一個refreshData()的方法——傳新的數據進來然後notifyDataSetChanged()。基於這些點,我寫了一個BaseAdapter基類:
/** * Adapter基類. * 適用於只有單個Item的RecyclerView. * * Created by lypeer on 16-5-24. */ public abstract class BaseAdapter<V> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { /** * 裝載了每個Item的Value的列表 */ private List<V> mValueList; /** * 我寫的一個接口,通過回調分發點擊事件 */ private OnItemClickListener<V> mOnItemClickListener; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return createViewHolder(parent.getContext(), parent); } @Override @SuppressWarnings("unchecked")//一定會是BaseViewHolder的子類,因為createViewHolder()的返回值 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { //BaseViewHolder是我抽象出來的RecyclerView.ViewHolder的基類,下面會有詳細講解 ((BaseViewHolder) holder).setData(mValueList.get(position), position, mOnItemClickListener); } /** * 設置每個Item的點擊事件 * @param listener */ public void setOnClickListener(OnItemClickListener<V> listener) { this.mOnItemClickListener = listener; } /** * 刷新數據 * @param valueList 新的數據列表 */ 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); }
它的子類在繼承它的時候需要指定泛型的具體類型,因為不同的Item也許其數據類型並不一樣,這樣就可以適應更多的Item。另外,其中提到了一個接口OnItemClickListener,這個接口很簡單:
/** * 點擊事件的接口 * Created by lypeer on 16-5-24. */ public interface OnItemClickListener<V> { /** * 當item被點擊的時候進行事件分發 * * @param itemValue 點擊的item傳遞的值 * @param viewID 點擊控件的id * @param position 被點擊的item的位置 */ void onItemClick(V itemValue, int viewID, int position); }
在使用它的時候同樣需要使用泛型——原因和上面一樣。
通過上面的BaseAdapter,我們把很多的共有操作都封裝在了基類裡面,而它的子類只需要根據需要新建不同的ViewHolder就行了——當然,這個viewHolder必須繼承自BaseViewHolder,而BaseViewHolder是什麼下面會有詳細講解。接下來是一個例子,假設我們現在在一個界面要有一個RecyclerView,它的每個Item的數據是一個String值,那麼怎麼使用我們的BaseAdapter簡化開發過程呢?
public class SampleAdapter extends BaseAdapter<String> { @Override protected BaseViewHolder createViewHolder(Context context, ViewGroup parent) { //SampleViewHolder繼承自BaseViewHolder return new SampleViewHolder(context, parent); } }
是的,你沒有看錯!就只有這麼幾行代碼!5秒完成!驚喜麼?!
你只需要新建一個SampleAdapter繼承自BaseAdapter,然後指定其泛型為String,再return new SampleViewHolder(context, parent) , 就完成了整個操作。
但是,有些讀者也許會有疑惑:也許我們需要在SampleViewHolder裡面做很多的操作呢?那豈不是只是把代碼轉換了一個地方而已,實質上並沒有什麼優化之處。
然而並不是。
BaseViewHolder
我一直認為,將ViewHolder寫在Adapter裡面是挺不明智的一個做法。這樣的話Adapter這個類的職責太重了,它做得事情太多了,從數據接收到界面綁定,從控件初始化到點擊事件分發,它簡直什麼都做完了。而這樣是很不好的。很輕易的,一個比較復雜的RecyclerView的Adapter的代碼就能達到成百上千行,這很可怕,這意味著這個類將變得冗雜且難以維護。那麼怎麼避免這種情況發生呢?我選擇了將ViewHolder分離,同時加重ViewHolder的職責,使它們能比較均衡。
直接看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來進行控件的綁定 ButterKnife.bind(this, itemView); } /** * 方便其子類進行一些需要Context的操作. * * @return 調用者的Context */ public Context getContext() { return itemView.getContext(); } /** * 抽象方法,綁定數據. * 讓子類自行對數據和view進行綁定 * * @param itemValue Item的數據 * @param position 當前item的position * @param listener 點擊事件監聽者 */ protected abstract void bindData(V itemValue, int position, OnItemClickListener listener); /** * 用於傳遞數據和信息 * * @param itemValue * @param position * @param listener */ public void setData(V itemValue, int position, OnItemClickListener listener) { bindData(itemValue, position, listener); } }
BaseViewHolder同樣采用了泛型,以適應不同的數據類型。同時,我在BaseViewHolder裡面使用了ButterKnife來簡化代碼,從此再也不用反反復復的findViewById了。
另外,大家可以看到BaseViewHolder裡面的構造方法中傳入了三個參數,但是在上面BaseAdapter的例子裡面SampleViewHolder的構造方法我們卻只傳入了它的前兩個構造參數,而第三個參數layoutRes並沒有傳進去,這是怎麼回事呢?是上面寫錯了麼?當然不是。BaseViewHolder的構造方法中有三個傳參是因為它需要三個傳參,而它的子類只有兩個傳參是因為它只能有兩個傳參。_BaseViewHolder必須要滿足它的super構造,所以必須要有那三個參數,而它的子類如果那三個參數都是由外界傳進來的,那麼它怎麼進行針對那個布局進行特異化的操作?它必須在類裡面顯式的指定Layout ID_——這其實是我很想優化的一個地方,因為這樣的話子類繼承BaseViewHolder之後還要修改它的構造方法,這是比較讓人不省心的,但是目前還沒有想到什麼好點子來優雅地優化它。
BaseViewHolder裡面有兩個看起來很像的方法:setData()和bindData(),然而實際上除了傳參相同,它們其他方面根本完全不一樣。setData()方法是一個public的方法,可以由BaseViewHolder的子類的對象調用,其作用是從外部傳入數據,以供ViewHolder所hold的那個view初始化,它可以說是一座傳輸信息的橋梁;而getData()是一個抽象的方法,它的具體實現在每一個BaseViewHolder的子類中,這些子類在這個方法裡面進行控件的綁定和初始化,以及對控件的點擊事件的處理等等。
說到點擊事件的處理,它的子類應該怎麼完成這件事呢?通過OnItemClickListener。我們可以借助於OnItemClickListener的接口回調來將需要處理的點擊事件傳遞到外界,然後由外界進行處理。那麼如果有多個控件的點擊事件需要處理怎麼辦?不用擔心,因為在OnItemClickListener的onClick方法中你需要傳入點擊的控件的id,這樣一來就可以在外界進行對傳入id的判斷,從而針對不同的id執行不同的點擊事件了。
接下來通過一個例子來看下具體怎麼用,就是上面的那個SampleViewHolder吧:
public class SampleViewHolder extends BaseViewHolder<String> { //一個普通的可點擊的TextView @Bind(R.id.is_tv_content) TextView mIsTvContent; public SampleViewHolder(Context context, ViewGroup root) { //修改了構造方法,在這裡顯式指定Layout ID super(context, root, R.layout.item_sample); } @Override protected void bindData(final String itemValue, final int position, final OnItemClickListener listener) { //在這裡完成控件的初始化,將其與數據綁定 if (itemValue != null) { mIsTvContent.setText(itemValue); } //如果需要有點擊事件,就通過listener把它傳遞出去 mIsTvContent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //如果外界沒有調用BaseAdapter.setOnClickListener(), //listener就為null if(listener == null){ return; } //listener不為null就將這個事件傳遞給外界處理 listener.onItemClick(itemValue , v.getId() , position); } }); } }
同樣很簡單方便快捷清晰。但是還是有幾點值得注意的地方。首先,不能忘了修改構造方法,顯式的在super裡面指定Layout ID,不然下一步都沒法做,那就懵逼了。另外,在調用listener.onClick()方法的時候必須進行listener的驗空——因為listener真的有可能為空!這樣的話非常容易產生空異常導致程序崩潰。
賓果,就這樣,SampleViewHolder就完成了,同樣幾乎不費吹灰之力。
結語
這篇博文的目的是分享一些我總結出來的東西,希望能讓大家的開發加速那麼一點點。當然,這裡面也許還有我沒發現的bug什麼的,如果大家在使用的過程中發現了問題請不要客氣,狠狠地砸給我吧!
最後還是再總結一下在有了BaseAdapter和BaseViewHolder的情況下怎麼最快的搞定RecyclerView的那一套:
ViewHolder相關
新建 XXXViewHolder 繼承自BaseViewHolder,指定泛型類型(也就是Item中數據的數據類型)。
刪掉構造方法中的layoutRes參數,在super裡面顯式指定Layout ID。
用ButterKnife綁定控件。
在bindData()方法中完成控件的初始化以及點擊事件的傳遞(別忘了listener的驗空)
Adapter相關
新建 XXXAdapter 繼承自BaseAdapter,指定泛型類型(也就是Item中數據的數據類型)。
return new XXXViewHolder(context, parent);
外界相關
綁定RecyclerView,新建XXXAdapter。
調用 BaseAdapter.refreshData()方法傳入數據列表。
如果有對點擊事件處理的需求,則調用BaseAdapter.setOnClickListener()方法。
目前我只做了針對RecyclerView中單個的Item的BaseAdapter,但是BaseViewHolder使可以通用的,並且其在多Item下也可以大大的簡化Adapter的體積。
以上就是對Android RecyclerView 數據綁定的資料整理,後續繼續補充相關資料謝謝大家對本站的支持!
前言Android應用中的APK安全性一直遭人诟病,市面上充斥著各種被破解或者漢化的應用,破解者可以非常簡單的通過破解工具就能對一個APK進行反編譯、破解、漢化等等,這樣
先上圖: 省市縣三級聯動,選地址經常用到用NumberPicker控件實現滑動,json數據解析使用fastjson框架使用很簡單,傳入一個String[
之前的一遍學習筆記主要就Android滑動沖突中,在不同方向的滑動所造成沖突進行了了解,這種沖突很容易理解,當然也很容易解決。今天,就同方向的滑動所造成的沖突進行一下了解
今天遇到一個問題,在listviev中加入傳進來的布局,上下滑動時,報錯The specified child already has a parent。 先貼原始代碼: