1. 程式人生 > >十秒鐘搞定 RecyclerView 資料繫結

十秒鐘搞定 RecyclerView 資料繫結

前言

在上一個專案裡有很多很多很多很多的RecyclerView,然後我需要寫很多很多很多很多的Adapter和Viewholder——多倒沒問題,但是裡面有很多重複的程式碼這就不能忍了!每一個Adapter和ViewHolder其實做的事情非常的像:檢視繫結,資料繫結,點選事件分發。還有啥?既然它們做的事情都一樣,為啥我們還要傻傻的繼續寫著重複的程式碼?

正文

BaseAdapter

通常我們要建立一個RecyclerView.Adapter是怎麼做的?

  • 接收一個數據列表
  • 重寫getItemCount()方法,確定Item的個數
  • 重寫onCreateViewHolder()方法,繫結Layout,新建一個我們自己寫的RecyclerView.ViewHolder
  • 重寫onBindViewHolder()方法,進行資料和檢視繫結
  • 由於RecyclerView沒有寫點選事件,把點選事件分發出去

基本上就是這個套路,或者再加一個refreshData()的方法——傳新的資料進來然後notifyDataSetChanged()。基於這些點,我寫了一個BaseAdapter基類:

Java
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 /** * Adapter基類. * 適用於只有單個Item的RecyclerView. * * Created by lypeer on 16-5-24. */publicabstractclassBaseAdapter<V>extendsRecyclerView.Adapter<RecyclerView
.ViewHolder>{/**     * 裝載了每個Item的Value的列表     */privateList<V>mValueList;/**     * 我寫的一個介面,通過回撥分發點選事件     */privateOnItemClickListener<V>mOnItemClickListener;@OverridepublicRecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,intviewType){returncreateViewHolder(parent.getContext(),parent);}@Override@SuppressWarnings("unchecked")//一定會是BaseViewHolder的子類,因為createViewHolder()的返回值publicvoidonBindViewHolder(RecyclerView.ViewHolder holder,intposition){//BaseViewHolder是我抽象出來的RecyclerView.ViewHolder的基類,下面會有詳細講解((BaseViewHolder)holder).setData(mValueList.get(position),position,mOnItemClickListener);}/**     * 設定每個Item的點選事件     * @param listener     */publicvoidsetOnClickListener(OnItemClickListener<V>listener){this.mOnItemClickListener=listener;}/**     * 重新整理資料     * @param valueList 新的資料列表     */publicvoidrefreshData(List<V>valueList){this.mValueList=valueList;notifyDataSetChanged();}@OverridepublicintgetItemCount(){returnmValueList==null?0:mValueList.size();}/**     * 生成ViewHolder     * @param context     * @param parent     * @return     */protectedabstractBaseViewHolder createViewHolder(Context context,ViewGroup parent);}

它的子類在繼承它的時候需要指定泛型的具體型別,因為不同的Item也許其資料型別並不一樣,這樣就可以適應更多的Item。另外,其中提到了一個介面OnItemClickListener,這個介面很簡單:

Java
123456789101112131415 /** * 點選事件的介面 * Created by lypeer on 16-5-24. */publicinterfaceOnItemClickListener<V>{/**     * 當item被點選的時候進行事件分發     *     * @param itemValue 點選的item傳遞的值     * @param viewID    點選控制元件的id     * @param position  被點選的item的位置     */voidonItemClick(VitemValue,intviewID,intposition);}

在使用它的時候同樣需要使用泛型——原因和上面一樣。

通過上面的BaseAdapter,我們把很多的共有操作都封裝在了基類裡面,而它的子類只需要根據需要新建不同的ViewHolder就行了——當然,這個viewHolder必須繼承自BaseViewHolder,而BaseViewHolder是什麼下面會有詳細講解。接下來是一個例子,假設我們現在在一個介面要有一個RecyclerView,它的每個Item的資料是一個String值,那麼怎麼使用我們的BaseAdapter簡化開發過程呢?

Java
1234567 publicclassSampleAdapterextendsBaseAdapter<String>{@OverrideprotectedBaseViewHolder createViewHolder(Context context,ViewGroup parent){//SampleViewHolder繼承自BaseViewHolderreturnnewSampleViewHolder(context,parent);}}

是的,你沒有看錯!就只有這麼幾行程式碼!5秒完成!驚喜麼?!

你只需要新建一個SampleAdapter繼承自BaseAdapter,然後指定其泛型為String,再return new SampleViewHolder(context, parent) , 就完成了整個操作。

但是,有些讀者也許會有疑惑:也許我們需要在SampleViewHolder裡面做很多的操作呢?那豈不是隻是把程式碼轉換了一個地方而已,實質上並沒有什麼優化之處。

然而並不是。

BaseViewHolder

我一直認為,將ViewHolder寫在Adapter裡面是挺不明智的一個做法。這樣的話Adapter這個類的職責太重了,它做得事情太多了,從資料接收到介面繫結,從控制元件初始化到點選事件分發,它簡直什麼都做完了。而這樣是很不好的。很輕易的,一個比較複雜的RecyclerView的Adapter的程式碼就能達到成百上千行,這很可怕,這意味著這個類將變得冗雜且難以維護。那麼怎麼避免這種情況發生呢?我選擇了將ViewHolder分離,同時加重ViewHolder的職責,使它們能比較均衡。

直接看BaseViewHolder的程式碼:

Java
12345678910111213141516171819202122232425262728293031323334353637383940414243 /** * ViewHolder基類 * * Created by lypeer on 16-5-27. */publicabstractclassBaseViewHolder<V>extendsRecyclerView.ViewHolder{publicBaseViewHolder(Context context,ViewGroup root,intlayoutRes){super(LayoutInflater.from(context).inflate(layoutRes,root,false));//這裡使用了ButterKnife來進行控制元件的繫結ButterKnife.bind(this,itemView);}/**     * 方便其子類進行一些需要Context的操作.     *     * @return 呼叫者的Context     */publicContext getContext(){returnitemView.getContext();}/**     * 抽象方法,繫結資料.     * 讓子類自行對資料和view進行繫結     *     * @param itemValue Item的資料     * @param position  當前item的position     * @param listener  點選事件監聽者     */protectedabstractvoidbindData(VitemValue,intposition,OnItemClickListener listener);/**     * 用於傳遞資料和資訊     *     * @param itemValue     * @param position     * @param listener     */publicvoidsetData(VitemValue,intposition,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吧:

Java
1234567891011121314151617181920212223242526272829303132 publicclassSampleViewHolderextendsBaseViewHolder<String>{//一個普通的可點選的TextView@Bind(R.id.is_tv_content)TextView mIsTvContent;publicSampleViewHolder(Context context,ViewGroup root){//修改了構造方法,在這裡顯式指定Layout IDsuper(context,root,R.layout.item_sample);}@OverrideprotectedvoidbindData(finalStringitemValue,finalintposition,finalOnItemClickListener listener){//在這裡完成控制元件的初始化,將其與資料繫結if(itemValue!=null){mIsTvContent.setText(itemValue);}//如果需要有點選事件,就通過listener把它傳遞出去mIsTvContent.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//如果外界沒有呼叫BaseAdapter.setOnClickListener(),//listener就為nullif(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的體積。

BaseAdapter和BaseViewHolder以及demo的原始碼 在這裡

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

相關推薦

秒鐘 RecyclerView 資料

前言 在上一個專案裡有很多很多很多很多的RecyclerView,然後我需要寫很多很多很多很多的Adapter和Viewholder——多倒沒問題,但是裡面有很多重複的程式碼這就不能忍了!每一個Adapter和ViewHolder其實做的事情非常的像:檢視繫結,資料繫結,點

分鐘pandas(Python資料預處理庫)

本文是對pandas官方網站上《10Minutes to pandas》的一個簡單的翻譯,原文在這裡。這篇文章是對pandas的一個簡單的介紹,詳細的介紹請參考:Cookbook 。習慣上,我們會按下面格式引入所需要的包: 一、           建立物件 1、可以

懂:MVVM模型以及VUE中的資料資料劫持釋出訂閱模式

## 搞懂:MVVM模式和Vue中的MVVM模式 ### MVVM * MVVM : `model - view - viewmodel`的縮寫,說都能直接說出來 `model`:模型,`view`:檢視,`view-Model`:檢視模型 * V:檢視,即瀏覽器最前端渲染的頁面 * M:模型,資

分鐘mongodb副本集

Java mongodb mongodb副本集配置 最近項目中用到了mongodb,由於是用mongodb來記錄一些程序的日誌信息和日常的統計,為了增加應用的可靠性,一直在找mongodb集群的一些資料,下面

分鐘macOS tensorflow + opencv配置

jpg bus main 個人 urn hub 打印 就是 學校 隔壁小白都簡單哭了 準備: MacOS(我的系統是10.12.6,比較懶很少更新) python 3.6(忘掉2.7吧~已經是遺留版本啦~下載地址 https://www.python.org/downloa

vue基礎4-資料

    1、v-bind 只能實現資料額單向繫結,從M到V,無法實現資料的雙向繫結       改變頁面輸入框的值,列印資料並未改變。      2、v-model 可以實現資料的雙向繫結,從M到V、V到M。 &nbs

關於SAP UI5資料我的一些原創內容

如何查詢SAP UI5官方關於資料繫結的文件: https://sapui5.hana.ondemand.com/ 點Documentation: Filter裡輸入data就能看到Data Binding的文件了。 下面是一些我的原創文章。 Jerry寫過一個如何自學UI5框架的系列文章,一共包含

ASP.NET 資料概述

使用 ASP.NET 資料繫結,您可以將任何伺服器控制元件繫結到簡單的屬性、集合、表示式和/或方法。如果您使用資料繫結,則當您在資料庫中或通過其他方法使用資料時,您會具有更大的靈活性。本文討論了下列資料繫結主題: 資料繫結概要 <%# %> 語法

vue 資料-動態樣式

vue 資料繫結-動態樣式 動態class名繫結的幾種方式 1. 物件方式 new Vue({ el: '#root', template: `<div :class="{active: isActive"></div>`, data ()

SpringMVC複雜資料——陣列實現批量刪除

前幾天學習SSM開發框架遇到了批量刪除的資料繫結問題,就從網上學習了一下,參考別人的部落格又加了點自己的見解寫了這篇部落格。 繫結陣列 在實際開發時,可能會遇到前端請求需要傳遞到後臺多個input的Name屬性相同的資料的情況(如批量刪除),這個情況用SpringMVC的Controller

WPF 關於資料大量元素到同一個物件。

當在同一個物件上繫結大量元素時,當然XMAL也是同樣可以繫結,但會使得XMAL的程式碼量很多,每個繫結的元素都要依此的與控制元件屬性值繫結。為了解決或者改善這個問題。下面介紹這種通用的繫結方式。 1.先看下面的例子   //此處是繫結的資料的類,自己可以自定義此類 usin

面試題:你能寫一個Vue的雙向資料嗎?

在目前的前端面試中,vue的雙向資料繫結已經成為了一個非常容易考到的點,即使不能當場寫出來,至少也要能說出原理。本篇文章中我將會仿照vue寫一個雙向資料繫結的例項,名字就叫myVue吧。結合註釋,希望能讓大家有所收穫。 1、原理 Vue的雙向資料繫結的原理相信大家也都十分了解了,主要是通過 Obje

Silverlight自定義資料控制元件應該如何處理IEditableObject和IEditableCollectionView物件

原文: Silverlight自定義資料繫結控制元件應該如何處理IEditableObject和IEditableCollectionView物件 原創文章,如需轉載,請註明出處。   最近在一直研究Silverlight下的資料繫結控制元件,發現有這樣兩個介面IEditableObject

SpringMVC_第三章(SpringMVC資料

1. 什麼是引數繫結 引數繫結,簡單來說就是客戶端傳送請求,而請求中包含一些資料,那麼這些資料怎麼到達 Controller ?這在實際專案開發中也是用到的最多的,那麼 SpringMVC 的引數繫結是怎麼實現的呢?下面我們來詳細的講解。 在springMVC之前 首先在使用spring

Vaadin Web應用開發教程 42 資料-Property介面

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

WPF---資料之PasswordBox(八)

一、概述 眾所周知,繫結的源既可以是依賴屬性也可以是普通的CLR屬性,而繫結的目標只能是依賴屬性。 控制元件PasswordBox的Password屬性不是依賴屬性,不可以作為繫結的目標與後臺資料進行繫結,而在MVVM模式中,前臺和後臺的繫結是經常需要的,為了達到這種目的,我們可以藉助附加屬性來實現Pas

WPF學習筆記 ComboBox的資料

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

WPFS資料(要是後臺類物件的屬性值發生改變,通知在“客戶端介面與之的控制元件值”也發生改變需要實現INotitypropertyChanged介面)

WPFS資料繫結(要是後臺類物件的屬性值發生改變,通知在“客戶端介面與之繫結的控制元件值”也發生改變需要實現INotitypropertyChanged介面) MainWindow.xaml 1 <Window x:Class="WpfApplication1.MainWindow" 2

d3 data()資料中的key函式

官網https://github.com/d3/d3-selection/blob/master/README.md#selection_data var data = [ {name: "Locke", number: 4}, {name: "Reyes", number: 8},

VUE.JS 使用axios資料請求時資料時 報錯 TypeError: Cannot set property 'xxxx' of undefined 的解決辦法

正常情況下在data裡面都有做了定義 在函式裡面進行賦值 這時候你執行時會發現,資料可以請求到,但是會報錯 TypeError: Cannot set property 'listgroup' of undefined  主要原因是: 在 then的內部不能使用Vue的例項