一個讓你愛不釋手的萬能Adapter(Kotlin+Databinding+CommonAdapter的碰撞)
感謝點進來看的各位技術小可愛,本篇文章為純乾貨,希望閱讀完本文的你能有所收穫,幫助大家提高專案的開發效率。
閱讀本文你將收穫:
1、簡潔好用的萬能介面卡一個
2、DataBinding的簡單使用
3、Kotlin和DataBinding結合使用遇到的問題
4、Github製作自己的線上Library
廢話不多說,先來直接看一下,CommonAdaper結合DataBinding後,咱們寫一個列表的Adapter程式碼成本,僅僅只需要7行程式碼。如下:
class ADA_ChapterFilter constructor(context: Context): CommonAdapter<DataBean, ItemLayoutBinding>(context) { override fun convert(viewBinding: ItemLayoutBinding?, holder: ViewHolder.BindingHolder?, bean: DataBean?, position: Int) { viewBinding!!.dataBean= bean } override fun itemLayoutId(): Int { return R.layout.item_layout } }
是,你沒看沒錯,就是這麼簡潔!!!省去了一大堆資料裝載時的setText等冗餘程式碼。下面是RecyclerView實現的列表效果:

example.gif
《一》本篇簡介
關於通用的介面卡,相信大家也看過不少部落格,如果還有在用傳統的方式寫列表介面卡的新手,那要趕快跟緊步伐啦,因為你可能已經落後不是一點點了哦。當然,其實即使是要自己去手寫一套萬能Adapter,也並不是很困難的事情,所以大家不需要畏懼,其實核心思想就是程式碼的封裝和抽象,以及一些設計模式的運用,感興趣的可以自己動手試試。本篇文章的基礎,是鴻洋大神的 Android/baseAdapter" target="_blank" rel="nofollow,noindex">BaseAdapter ,支援ListView和RecyclerView的Adapter,且能支援多型別列表的適配 ,能很好的滿足日常專案開發的需求。本篇文章就是在他寫的BaseAdapter的基礎上,進行了改造,所以下面開始介紹經我簡單改造後的DataBindingCommonAdapter。
《二》使用方法
(1)在你的工程根目錄下新增:
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
(2)在你的app的build.gradle下新增依賴
dependencies { implementation 'com.github.GraceJoJo:DataBindingBaseAdapter:1.0.1' }
(3)RecyclerView中Adapter的用法:
class ADA_RecyclerItem constructor(context: Context): CommonAdapter<DataBean, ItemLayoutBinding>(context) { override fun convert(viewBinding: ItemLayoutBinding?, holder: ViewHolder.BindingHolder?, bean: DataBean?, position: Int) { viewBinding!!.dataBean = bean } override fun itemLayoutId(): Int { return R.layout.item_layout } }
(4)ListView或者GridView的Adapter的用法:
class ADA_ListItem constructor(context: Context): CommonAdapterListView<DataBean, ItemLayoutBinding>(context) { override fun convert(viewBinding: ItemLayoutBinding?, holder: ViewHolderListView?, bean: DataBean?, position: Int) { viewBinding!!.dataBean = bean } override fun itemLayoutId(): Int { return R.layout.item_layout } }
《三》對BaseAdapter的改造思路
不瞭解鴻洋大神的萬能適配方案的,可以點選: BaseAdapter 先學習瞭解一下,也可以直接下載文末我改造後的案例。本篇文章著重講解RecyclerView的Adapter的改造,完整的原始碼請去文末下載哦~
我們來看看,BaseAdapter未改造前,RecyclerView的Adapter寫法可能是這樣的:

image.png
其實相比原始的寫法還是很簡單的,但是如果涉及的欄位比較多,那麼就會有大量的setText()等,雖然BaseAdapter已經很大程度上簡化了Adapter,但是我們還是每次都要寫很多重複的簡單程式碼。
分析:歸根結底,其實寫Adapter無非就是下面幾個要素
(1)寫一個item佈局檔案
(2)告訴Adapter每個item對應的bean是什麼
(3)繫結資料:給item佈局中的控制元件設定對應的資料
①item佈局,不管你如何簡化,都得寫上,這個毋庸置疑;
②可以看到未改造前的BaseAdapter,已經將第二點bean類以泛型的形式抽離出來了;
③那麼我們看看第三點,是不是可以對它做點什麼。借鑑著把bean類抽離一個泛型的思想,結合DataBinding,每個item佈局檔案會對應一個ViewDataBinding,所以我把ViewDataBinding抽離出一個泛型出來。
1、在CommonAdapter中抽離出ViewDataBinding的泛型:

image.png
2、在ViewHolder中使用DataBinding繫結佈局:

image.png
3、在onBindViewHolder中把佈局對應的某一個具體的ViewBinding傳出去,供資料更新時給控制元件設定資料使用:

image.png
4、改造後的使用:
class ADA_RecyclerItem constructor(context: Context): CommonAdapter<DataBean, ItemLayoutBinding>(context) { override fun convert(viewBinding: ItemLayoutBinding?, holder: ViewHolder.BindingHolder?, bean: DataBean?, position: Int) { viewBinding!!.dataBean = bean } override fun itemLayoutId(): Int { return R.layout.item_layout } }
《四》DataBinding的簡單介紹—— MVVM
DataBinding不知道大家熟悉與否,不管怎樣,我都要在這裡隆重的介紹一下它,因為讓程式碼如此簡潔的大功臣,正是DataBinding。
(1)DataBinding是什麼?
① DataBinding是一個support library,所以它可以支援所有的android sdk,最低可以到android2.1(API7)。
② 使用DataBinding需要Android Gradle外掛的支援,版本至少在1.5以上,需要的Android studio的版本在1.3以上。
(2)首先,我們在需要用到DataBinding的module或者library的build.gradle中,使其支援DataBinding。android{ }下新增如下程式碼:
// 開啟Data Binding , 這樣我們可以通過宣告式佈局以精簡的程式碼來繫結應用程式邏輯和佈局 dataBinding{ enabled = true }
(3)XML佈局中做宣告資料繫結

image.png
例如:以本例Adapter對於的item_layout的佈局為例,寫法如下:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@android:color/white"> <!--data節點下一個variable節點代表一個變數, name屬性根據需要自己取名,type為需要用到的Model的全路徑, 功能相當於寫程式碼的時候引入一個類的功能--> <data> <variable name="dataBean" type="com.example.jojo.databinding_commonadapter.DataBean"></variable> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="15dp" android:paddingRight="15dp"> <FrameLayout android:id="@+id/ll_rank" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true"> <TextView android:id="@+id/tv_rank_num" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="3dp" android:layout_marginRight="5dp" android:textColor="#2E3439" android:textSize="12sp" android:textStyle="bold" /> </FrameLayout> <ImageView android:id="@+id/iv_cover" android:layout_width="60dp" android:layout_height="80dp" android:layout_centerVertical="true" android:layout_marginLeft="12dp" android:layout_toRightOf="@+id/ll_rank" android:padding="1px" android:scaleType="fitXY" app:imageUrl="@{bean.covor_url}" /> <LinearLayout android:id="@+id/ll_info" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginTop="12dp" android:layout_toRightOf="@+id/iv_cover" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="7dp" android:includeFontPadding="false" android:text="@{bean.name_cn}" android:textColor="#2E3439" android:textSize="14sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="6dp" android:text="@{bean.author}" android:textColor="#666666" android:textSize="12sp" /> <TextView android:id="@+id/tv_comment" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="11dp" android:ellipsize="end" android:inputType="textMultiLine" android:lines="2" android:text="@{bean.comment}" android:textColor="#999999" android:textSize="10sp" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@+id/ll_info" android:layout_marginTop="10dp" android:background="#f9f9f9"></View> </RelativeLayout> </layout>
(3)定義資料繫結的Data物件:
data class DataBean constructor(val name_cn: String, val comment: String, val author: String, val covor_url: String)
(4)使用DataBindingUtil,繫結佈局與資料。
Android studio會根據layout檔案自動生成一個預設的Binding類,類名是根據layout檔名生成的,並有"Binding"字尾結束。
情景1:在Activity中
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User("Test", "User"); binding.setUser(user); //資料更新,設定給繫結的控制元件,此時即完成了頁面的資料重新整理
情景2:在Fragment中
@Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // Inflate the layout for this fragment FragmentLayoutBindingviewBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_layout, container, false); return viewDataBinding.getRoot(); }
關於DataBinding,更多詳細的介紹及一些高階用法,我就不在本文多加贅述了
總結:
通過簡單的改造,我們寫Adapter時,就只需要給Adapter一個佈局、一個具體的bean、一個佈局檔案對應的具體的ViewDataBinding,然後在佈局檔案中使用DataBinding把資料繫結寫好,一個Adapter的工作就完成了。
結合了DataBinding後,我們將資料與頁面的繫結,在寫佈局的時候就把頁面控制元件對應的資料綁定了,這樣省去了大量的BindView操作和對view設定資料的處理。
《五》遇到的問題及解決
(1)如果不熟悉DataBinding的朋友可能會有疑問。如果是載入圖片或者是某個控制元件繫結的資料展示需要特殊處理咋辦?這就是DataBinding的知識了,這裡我簡單說一下這種情況的處理方法。
DataBinding有個BindingAdapter,它的功能是用來設定view的屬性值。
假設你要在佈局中顯示一個圓角圖片,咋辦?你可以新建一個類,叫ViewBindingAdapter。
public class ViewBindingAdapter { @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView imageView, String url) { RequestOptions requestOptions = new RequestOptions() .priority(Priority.HIGH) .transform(new CircleCrop()); Glide.with(MyApplication.context) .load(url) .apply(requestOptions) .transition(new DrawableTransitionOptions().crossFade()) .into(imageView); } @BindingAdapter({"app:date_text"}) public static void setDateText(TextView tv, String text) { //處理文字顯示 tv.setText(text + "年"); } }
佈局檔案中引用:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@android:color/white"> <!--data節點下一個variable節點代表一個變數, name屬性根據需要自己取名,type為需要用到的Model的全路徑, 功能相當於寫程式碼的時候引入一個類的功能--> <data> <variable name="dataBean" type="com.example.jojo.databinding.DataBean"></variable> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_cover" android:layout_width="wrap_content" android:layout_height="wrap_content" app:imageUrl="@{dataBean.covor_url}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:date_text="@{dataBean.time}" android:textSize="14sp" /> </RelativeLayout> </layout>
像這樣,你可以在ViewBindingAdapter裡建立多個@BindingAdapter註解的方法,來特殊處理你在佈局檔案要給控制元件繫結的資料值。
(2)Kotlin下使用DataBinding遇到的問題
dataBinding+kotlin環境下會報錯:Error: Unresolved reference: databinding
解決:在app的build.gradle下新增,sync now即可恢復正常。
我的app的Android plugin版本為 classpath 'com.android.tools.build:gradle:3.0.1'
dependencies { kapt 'com.android.databinding:compiler:3.0.1' } kapt { generateStubs = true }
寫在結尾:
對本文有問題的朋友歡迎大家留言交流哦!如果覺得本文對你有幫助,留下個小心心再走吧,謝謝大家的支援~
(1)本文完整Demo請戳 github地址
(2)感謝鴻洋大神的 BaseAdapter
(3) GitHub上製作自己的Library,直接compile使用