概念
DataBind 就是 基于apt技術(shù),幫我們生成了一些模板代碼,這些模板代碼大概解決了如下操作:
- 控件變量的聲明,類似如下:
@NonNull
public final TextView tv1;//自動(dòng)解決了類型匹配的問題,不用擔(dān)心自己手抖觸發(fā)類型轉(zhuǎn)換異常了
- 控件的查找賦值,相當(dāng)于自動(dòng)幫我們完成了類似如下操作:
tv1 = findViewById(R.id.tv1) //確保了自己腦子卡,忘記給聲明的變量賦值,引發(fā)空指針
- 控制的數(shù)據(jù)填充操作,也就是其本意數(shù)據(jù)綁定,類似自動(dòng)完成了如下操作:
tv1.setText(user.getName())
讀前須知
- 官網(wǎng)連接:數(shù)據(jù)綁定庫
- 本文只講使用層面的對(duì)應(yīng)解析,不涉及原理流程之類,這點(diǎn)將在下一篇完善
- 本文主要基于官網(wǎng)的使用實(shí)例,集合kapt生成的相關(guān)代碼,來吃透用法背后的真實(shí)面紗(源碼)
- 本文不涉及配置,基礎(chǔ)引用等,需要有一定的DataBinding使用經(jīng)驗(yàn),但是只用但是不知道為啥這么用的大佬
按照官網(wǎng)的順序一點(diǎn)一點(diǎn)來
最常見的TexView的text填充
< android:text="@{viewModel.name}" />
對(duì)應(yīng)的賦值模板代碼如下:
//聲明數(shù)據(jù)對(duì)應(yīng)數(shù)據(jù)變量 java.lang.String viewModelName = null; //數(shù)據(jù)變量賦值 viewModelName = viewModel.getName(); //數(shù)據(jù)綁定View androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, viewModelName); //TextViewBindingAdapter是DataBinding庫給我們提供的一個(gè)數(shù)據(jù)綁定適配器 //這個(gè)方法翻譯過來就是接管了android:text的屬性的賦值邏輯,也就是當(dāng)遇到上面寫法時(shí),會(huì)通過下面的靜態(tài)方法進(jìn)行賦值 //官方提供的了很多,可以參考著進(jìn)行更多屬性賦值功能擴(kuò)展,這絕對(duì)是最好的copy對(duì)象 @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { //下面實(shí)現(xiàn)很貼心,對(duì)新老內(nèi)容進(jìn)行了比較,防止了重復(fù)調(diào)用view.setText(text);引起的過渡繪制,很值得我們學(xué)習(xí)哦 final CharSequence oldText = view.getText(); if (text == oldText || (text == null && oldText.length() == 0)) { return; } if (text instanceof Spanned) { if (text.equals(oldText)) { return; // No change in the spans, so don't set anything. } } else if (!haveContentsChanged(text, oldText)) { return; // No content changes, so don't set anything. } view.setText(text); }
帶表達(dá)式的
< android:visibility="@{viewModel.age > 10 ? View.VISIBLE : View.GONE}" />
對(duì)應(yīng)的賦值代碼塊如下(上面只是一個(gè)簡(jiǎn)單的表達(dá)式,其實(shí)還有很多,但是官方并不太建議在布局寫太復(fù)雜的表達(dá)式,這樣會(huì)搞的布局文件很亂,按照J(rèn)etpack的整體思路,數(shù)據(jù)的邏輯處理,應(yīng)該的ViewModel中處理,布局里最好是取最終值就好,這里吐槽一點(diǎn),有些說法是Databind會(huì)搞的布局文件很亂,其實(shí)是用復(fù)雜了而已,人家官方本意其實(shí)指向讓你進(jìn)行數(shù)據(jù)綁定,并不想讓你在布局里做太復(fù)雜的數(shù)據(jù)邏輯)
//聲明變量,這個(gè)名字很長(zhǎng) int viewModelAgeInt10ViewVISIBLEViewGONE = 0; //變量賦值 viewModelAgeInt10ViewVISIBLEViewGONE = ((viewModelAgeInt10) ? (android.view.View.VISIBLE) : (android.view.View.GONE)); //數(shù)據(jù)綁定 // ?咦,這個(gè)咋沒適配器呢。因?yàn)镈atabind,針對(duì)屬性有對(duì)應(yīng)setXXX方法會(huì)默認(rèn)調(diào)用其setXXX方法就好,無需提供Adapter //那android:text,也有setText()呀,為啥上面有呢,因?yàn)楣俜接X得那個(gè)方法太簡(jiǎn)單了,所以給提供了更優(yōu)化的方法,以達(dá)到優(yōu)化目的 //也就是說默認(rèn)的會(huì)調(diào)用屬性對(duì)應(yīng)的setXXX方法,如果有適配器定制的話就調(diào)用定制的 this.tv3.setVisibility(viewModelAgeInt10ViewVISIBLEViewGONE);
Null合并運(yùn)算符
< android:text="@{viewModel.name??viewModel.lastName}" />
對(duì)應(yīng)的賦值代碼塊
//聲明變量 java.lang.String viewModelNameJavaLangObjectNullViewModelLastNameViewModelName = null; //給變量賦值,本質(zhì)還是用了我們?nèi)\(yùn)算符,也就是這個(gè)就是個(gè)語法糖 viewModelNameJavaLangObjectNullViewModelLastNameViewModelName = ((viewModelNameJavaLangObjectNull) ? (viewModelLastName) : (viewModelName)); //給View填充數(shù)據(jù),講過了不啰嗦 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, viewModelNameJavaLangObjectNullViewModelLastNameViewModelName);
視圖引用
< android:text="@{tv3.text}" />
適用兩個(gè)組件取值一致的場(chǎng)景
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv3, stringValueOfViewModelAge); androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv4, stringValueOfViewModelAge);
其實(shí)本質(zhì)是兩個(gè)組件使用了統(tǒng)一個(gè)數(shù)據(jù)變量
集合
<!-- 要手動(dòng)導(dǎo)入,主語xml里不支持‘<’符號(hào),要用<代替 --> <import type="java.util.List"/> <variable name="listdata" type="List<String>" /> <!-- 使用 --> <TextView android:text="@{viewModel.list[1]}"/>
對(duì)應(yīng)代碼生成的代碼
import java.util.List; java.util.List<java.lang.String> viewModelList = null; java.lang.String viewModelList1 = null; viewModelList = viewModel.getList(); if (viewModelList != null) { // read viewModel.list[1] viewModelList1 = getFromList(viewModelList, 1); } androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView14, viewModelList1); //這個(gè)方法是ViewDatabinding基類提供的方法,與之對(duì)應(yīng)還有其它集合類方法,用于獲取集合中某個(gè)索引值用 protected static <T> T getFromList(List<T> list, int index) { if (list == null || index < 0 || index >= list.size()) { return null; } return list.get(index); }
字符串字面量
這個(gè)就簡(jiǎn)單的說下使用,場(chǎng)景上就是咱們的值是在雙引號(hào)內(nèi)的,里面如果需要字面常量時(shí)不能再用雙引號(hào),要用單引號(hào);當(dāng)然如果外層用單引號(hào)內(nèi)層就可以用雙引號(hào)了,總之就是不能同時(shí)出現(xiàn)兩個(gè)雙引號(hào)
<!-- 外單內(nèi)雙 --> < android:text='@{"寫死的值"}' /> <!-- 外雙內(nèi)單 --> < android:text="@{map[`firstName`]}" />
//到了編譯后就是用的死值,不會(huì)為其生成變量 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv5, "寫死的值");
資源
主要是資源文件的動(dòng)態(tài)話
<string name="content">I am %s , age is %d</string> < android:text='@{@string/content(viewModel.name,viewModel.age)}' />
java.lang.String tv6AndroidStringContentViewModelNameViewModelAge = null; //可以看到本質(zhì)上還是調(diào)用了tv6.getResources().getString()的方法 tv6AndroidStringContentViewModelNameViewModelAge = tv6.getResources().getString(R.string.content, viewModelName, viewModelAge); androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv6, tv6AndroidStringContentViewModelNameViewModelAge);
方法引用
事件處理的一種方式,官方的解釋是:
- 在表達(dá)式中,您可以引用符合監(jiān)聽器方法簽名的方法。當(dāng)表達(dá)式求值結(jié)果為方法引用時(shí),數(shù)據(jù)綁定會(huì)將方法引用和所有者對(duì)象封裝到監(jiān)聽器中,并在目標(biāo)視圖上設(shè)置該監(jiān)聽器。如果表達(dá)式的求值結(jié)果為 null,則數(shù)據(jù)綁定不會(huì)創(chuàng)建監(jiān)聽器,而是設(shè)置 null 監(jiān)聽器。
- 事件可以直接綁定到處理腳本方法,類似于為 Activity 中的方法指定 android:onClick 的方式。與 View
onClick` 特性相比,一個(gè)主要優(yōu)點(diǎn)是表達(dá)式在編譯時(shí)進(jìn)行處理,因此,如果該方法不存在或其簽名不正確,則會(huì)收到編譯時(shí)錯(cuò)誤。
- 方法引用和監(jiān)聽器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的。如果您希望在事件發(fā)生時(shí)對(duì)表達(dá)式求值,則應(yīng)使用監(jiān)聽器綁定。
好難懂是吧,那我們接下來就根據(jù)栗子和源碼去理解吧
首先定義一個(gè)方法引用,如下
class ListenerHandler { fun tvOnClick(view: View){ Toast.makeText(view.context,"aaaa",Toast.LENGTH_LONG).show() } }
這個(gè)啥特點(diǎn)呢就是方法的參數(shù)與返回值必須與對(duì)應(yīng)事件的參數(shù)類型一致,方法名隨意,對(duì)應(yīng)到官方的一句話就是”引用符合監(jiān)聽器方法簽名的方法“
<variable name="clickHandler" type="org.geekbang.databindingtest.ListenerHandler" />
來個(gè)錯(cuò)誤的示范
fun tvOnClick(view: View) --> fun tvOnClick(context: Context);也就是方法簽名搞錯(cuò)了,會(huì)咋樣呢
會(huì)直接有個(gè)紅線,提示錯(cuò)了,也就是不和規(guī)范在編譯器就不行了
修正過來的寫法android:onClick="@{clickHandler::tvOnClick}"
那么最終編譯出來的相關(guān)代碼是啥呢
//老樣子,根據(jù)文件里的東東生成一個(gè)變量 android.view.View.OnClickListener clickHandlerTvOnClickAndroidViewViewOnClickListener = null; private OnClickListenerImpl mClickHandlerTvOnClickAndroidViewViewOnClickListener; if (clickHandler != null) { clickHandlerTvOnClickAndroidViewViewOnClickListener = (((mClickHandlerTvOnClickAndroidViewViewOnClickListener == null) ? (mClickHandlerTvOnClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mClickHandlerTvOnClickAndroidViewViewOnClickListener).setValue(clickHandler)); }
翻譯下就是,如果mXXX==null,則new OnClickListenerImpl(),否則mXXX.setValue(clickHandler)更新下值
先看否則 clickHandler,很明顯就是我們?cè)诓季治募械?lt;variable name="clickHandler">
核心還是那個(gè)OnClickListenerImpl,看下源碼public static class OnClickListenerImpl implements android.view.View.OnClickListener{ private org.geekbang.databindingtest.ListenerHandler value; public OnClickListenerImpl setValue(org.geekbang.databindingtest.ListenerHandler value) { this.value = value; return value == null ? null : this; } @Override public void onClick(android.view.View arg0) { this.value.tvOnClick(arg0); } }
這個(gè)類能解釋好多官方解釋
- 這個(gè)類和實(shí)例是編譯時(shí)就創(chuàng)建,對(duì)應(yīng)官方的話:方法引用和監(jiān)聽器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的
- 這個(gè)類里持有一個(gè)變量org.geekbang.databindingtest.ListenerHandler value,這個(gè)變量的類型是我們的自定義的實(shí)現(xiàn)的類型,值也很明顯就是我們聲明的那個(gè)clickHandler;這里對(duì)應(yīng)官方的話:數(shù)據(jù)綁定會(huì)將方法引用和所有者對(duì)象封裝到監(jiān)聽器中
- 接口的實(shí)現(xiàn)最終調(diào)用的是value對(duì)應(yīng)的方法;這也就能解釋通為啥定義的方法一定要符合監(jiān)聽器的方法簽名了,也就是參數(shù)上要對(duì)應(yīng)好,從這里我們可以發(fā)現(xiàn),其實(shí)不一定參數(shù)類型完全一致,只要是事件方法參數(shù)的子類類型就可以了,不過一般設(shè)置接口時(shí)就會(huì)根據(jù)依賴倒置規(guī)則確定了類型上不能再具體了。
最后肯定是設(shè)置值了,這一些列操作編譯時(shí)相當(dāng)于都把代碼給我們寫好了,至此所謂的方法引用方式綁定事件處理就通了
this.tv1.setOnClickListener(clickHandlerTvOnClickAndroidViewViewOnClickListener);
監(jiān)聽器綁定
也是事件處理的一種方式,官方的解釋來一波:
- 這些是在事件發(fā)生時(shí)進(jìn)行求值的 lambda 表達(dá)式。數(shù)據(jù)綁定始終會(huì)創(chuàng)建一個(gè)要在視圖上設(shè)置的監(jiān)聽器。事件被分派后,監(jiān)聽器會(huì)對(duì) lambda 表達(dá)式進(jìn)行求值。
- 監(jiān)聽器綁定是在事件發(fā)生時(shí)運(yùn)行的綁定表達(dá)式。它們類似于方法引用,但允許您運(yùn)行任意數(shù)據(jù)綁定表達(dá)式。
- 在方法引用中,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)匹配。在監(jiān)聽器綁定中,只有您的返回值必須與監(jiān)聽器的預(yù)期返回值相匹配(預(yù)期返回值無效除外)
定義,聲明,使用
class ListenerHandler { fun onClickByInfo(view:View,text:CharSequence){ Toast.makeText(view.context,text,Toast.LENGTH_LONG).show() } }
<variable name="clickHandler" type="org.geekbang.databindingtest.ListenerHandler" /> < android:onClick="@{(view)->clickHandler.onClickByInfo(view,viewModel.name)}" />
理一下生成的相關(guān)代碼,我們倒著看比較好,這里倒著看下
//給tv設(shè)置監(jiān)聽,這里的callBack肯定是一個(gè)OnClickListener this.tv2.setOnClickListener(mCallback1); // 果不其然,直接就是 @Nullable private final android.view.View.OnClickListener mCallback1; // 那他賦值是誰呢,看下面,這個(gè)有兩個(gè)參數(shù),傳了this->ActivityMainBinding,還有一個(gè)1?,這個(gè)1是干啥的。。。現(xiàn)在不清楚 mCallback1 = new org.geekbang.databindingtest.generated.callback.OnClickListener(this, 1); //看看這個(gè)類的實(shí)現(xiàn) package org.geekbang.databindingtest.generated.callback; public final class OnClickListener implements android.view.View.OnClickListener { final Listener mListener; final int mSourceId; public OnClickListener(Listener listener, int sourceId) { mListener = listener; mSourceId = sourceId; } @Override public void onClick(android.view.View callbackArg_0) { //我們點(diǎn)擊按鈕時(shí)會(huì)調(diào)這里,這個(gè)的具體實(shí)現(xiàn)交給了內(nèi)部接口實(shí)例mListener的_internalCallbackOnClick方法去實(shí)現(xiàn)了 mListener._internalCallbackOnClick(mSourceId , callbackArg_0); } public interface Listener { void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0); } } 接下來就看那個(gè)接口的實(shí)現(xiàn)在哪里,通過開始的賦值也能才到,實(shí)現(xiàn)在ActivityMainBinding,那么就看具體實(shí)現(xiàn) public final void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0) { boolean clickHandlerJavaLangObjectNull = false; java.lang.String viewModelName = null; org.geekbang.databindingtest.ListenerHandler clickHandler = mClickHandler; org.geekbang.databindingtest.MainViewModel viewModel = mViewModel; boolean viewModelJavaLangObjectNull = false; clickHandlerJavaLangObjectNull = (clickHandler) != (null); if (clickHandlerJavaLangObjectNull) { viewModelJavaLangObjectNull = (viewModel) != (null); if (viewModelJavaLangObjectNull) { viewModelName = viewModel.getName(); //上面都是變量聲明和檢測(cè),其實(shí)可以看出來就是各種非空邏輯的判斷,要確保不出現(xiàn)空指針 //這里最終調(diào)用了我們定義的方法 clickHandler.onClickByInfo(callbackArg_0, viewModelName); } } }
從上面源碼看,相比方法引用其實(shí)就是換了下寫法,同時(shí)支持非簽名參數(shù)了而已,因?yàn)檫@些代碼也都是編譯時(shí)都設(shè)置好了。
那么這兩種看都是將原來的onClick的具體實(shí)現(xiàn)最終轉(zhuǎn)給了我們自己寫的業(yè)務(wù)塊,只是方法參數(shù)上監(jiān)聽器比方法引用更加靈活,這里可以推斷出場(chǎng)景選擇,點(diǎn)擊事件不依賴于數(shù)據(jù)邏輯時(shí)用方法引用就好,如果依賴于數(shù)據(jù),比如我們的Rv的Item里的點(diǎn)擊需要把Item的data帶出去,就可以用監(jiān)聽器引用了。
可觀察數(shù)據(jù)對(duì)象
這個(gè)其實(shí)在實(shí)際中都以LiveData代替了,這最終是如何運(yùn)行的,且看下回分解。我們就通過一個(gè)簡(jiǎn)單的LiveData實(shí)例看看有啥相關(guān)代碼。
val descriptionInfo = MutableLiveData("簡(jiǎn)介")
< android:text="@={viewModel.descriptionInfo}" />
倒著看看相關(guān)代碼
//賦值 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvDes, viewModelDescriptionInfoGetValue); //相關(guān)變量聲明,能看到有兩個(gè)變量,一個(gè)是值,一個(gè)是LiveData androidx.lifecycle.MutableLiveData<java.lang.String> viewModelDescriptionInfo = null; java.lang.String viewModelDescriptionInfoGetValue = null; //值肯定通過LiveData獲取的 if (viewModelDescriptionInfo != null) { viewModelDescriptionInfoGetValue = viewModelDescriptionInfo.getValue(); } // LiveData通過變量賦值 if (viewModel != null) { // read viewModel.descriptionInfo viewModelDescriptionInfo = viewModel.getDescriptionInfo(); } //賦值后還有個(gè)下面的方法,從這里順下去應(yīng)該能找出是如何響應(yīng)LiveData變化的 updateLiveDataRegistration(1, viewModelDescriptionInfo); /** * 更新liveData的的注冊(cè)器 * 本地字段id * LiveData自己,算是一個(gè)observable */ protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) { mInLiveDataRegisterObserver = true; try { //最終給了updateRegistration,參數(shù)CREATE_LIVE_DATA_LISTENER,這個(gè)方法最其實(shí)就是經(jīng)過關(guān)聯(lián)判斷,合理的去給LiveData去更新下觀察者,就不展開了 return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER); } finally { mInLiveDataRegisterObserver = false; } } //CREATE_LIVE_DATA_LISTENER是啥?可以看到其本質(zhì)是LiveDataListener,是ViewDataBinding的靜態(tài)常量。那就瞅一眼,有點(diǎn)多,我們只關(guān)注下核心的 //implements Observer是一個(gè)觀察者 private static class LiveDataListener implements Observer, ObservableReference<LiveData<?>> { //一個(gè)監(jiān)聽相關(guān)的數(shù)據(jù)包裝類,將各個(gè)參數(shù)存到這里面了 final WeakListener<LiveData<?>> mListener; //略... mListener = new WeakListener(binder, localFieldId, this, referenceQueue); //略... //會(huì)給LiveData設(shè)置監(jiān)聽 liveData.observe(newOwner, this); //略... //響應(yīng)監(jiān)聽變化 public void onChanged(@Nullable Object o) { ViewDataBinding binder = mListener.getBinder(); if (binder != null) { binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0); } } }
從上線我們能看到當(dāng)生性周期狀態(tài)變化后,會(huì)交由ViewDataBinding的handleFieldChange去響應(yīng)變化,接下來我們跟一下這條線的主要代碼
//如果字段變化后會(huì)走requestRebind boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { requestRebind(); } //requestRebind是請(qǐng)求重新綁定的意思,最終繞來繞去會(huì)走到executeBindings();而executeBindings是執(zhí)行綁定的意思,最終會(huì)回到我們開始的賦值階段
從上面看,我們的LiveData數(shù)據(jù)的管理還是有點(diǎn)復(fù)雜的,主要是很多預(yù)制的東西,本質(zhì)上還是注冊(cè)觀察者和響應(yīng)觀察者而已,只是將這一套流程模板化了而已
綁定適配器
自己的理解
- 這個(gè)官網(wǎng)講述的都已經(jīng)很詳細(xì)了,這里主要是做做總結(jié),加深下理解,沒興趣的可以直接看官網(wǎng)。
- 綁定的實(shí)質(zhì)是設(shè)置控件的某個(gè)屬性,如setText是改變文字,setAlpha是改變透明度,那么要改變這個(gè)屬性可能需要一些邏輯去定制,比如setText,如果新舊值沒有變化就沒必要設(shè)置了,多刷新一次而已,那么適配器的本質(zhì)作用就是為設(shè)置屬性提供自定義實(shí)現(xiàn)方式,類似于RecycleView中我們將布局填充交給Adapter一樣,這里是我們將某個(gè)屬性的修改交給了一個(gè)Adapter而已。
適配器的定制原則
- 理論上任何屬性都可以定制
- 自動(dòng)選擇:有setter方法的都可以直接搞
- 有屬性有對(duì)應(yīng)的setter方法,可以直接用;例如android:gravity="@{vm.gravity}"
- 沒有屬性,但是有setter方法,可以通過app:xxx="xxx"方式使用;例如app:scrimColor="@{@color/scrim}"
- 指定自定義方法名稱:有屬性,但是沒有對(duì)應(yīng)的setter,但是呢有其它名稱的方法可以單獨(dú)修改這個(gè)屬性,可以將屬性及方法建立關(guān)聯(lián)即可。例如android:tint這個(gè)屬性,木有setTint方法,但是有setImageTintList方法是用來設(shè)置各個(gè)屬性的,我們通過一下方法進(jìn)行關(guān)鍵即可。示例:
其實(shí)這個(gè)有,但是很少,首先一般寫法都是用對(duì)應(yīng)setter方法,及時(shí)需要關(guān)聯(lián)的,大多數(shù)DataBinding框架已經(jīng)幫我們建立關(guān)聯(lián)了,發(fā)現(xiàn)木有時(shí)到androidx.databinding.adapters下找找看。@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
- 完全自定義:這個(gè)就是既沒有默認(rèn),也無法1v1關(guān)聯(lián)的場(chǎng)景了;例如我們有android:paddingLeft屬性,但沒有該屬性的sePaddingLeft,有改變的方法setPadding但這個(gè)哥們是四個(gè)參數(shù)是改四個(gè)padding值用的,也就是完全驢唇對(duì)不上馬嘴,就只能自定義了,這也是我們大多數(shù)場(chǎng)景了。
- 這個(gè)主要是單參數(shù),組合參數(shù)的情況,官網(wǎng)給了明確的示例,并且官方庫也提供了很多封裝,比著葫蘆畫瓢就好。注意在Kotlin中比官方更簡(jiǎn)單點(diǎn),因?yàn)镵otlin可以用擴(kuò)展函數(shù)實(shí)現(xiàn),省了一個(gè)參數(shù)的聲明
- @BindingAdapter 其實(shí)讀一下這個(gè)注解的源碼及注釋就一通百通了
對(duì)象轉(zhuǎn)換
- 我們目標(biāo)上在綁定數(shù)據(jù)時(shí)需要確保與屬性的值類型一致,如我們gravity屬性需要一個(gè)int,我們給一個(gè)字符串類型的肯定不合適。但是某些邏輯下我們得到的可能并不是int類型,這就需要轉(zhuǎn)換了。轉(zhuǎn)換的目的是讓值能符合屬性設(shè)置的類型要求。
- 自動(dòng)轉(zhuǎn)換:有些屬性可以支持多種類型,比如setText,可以是int的resId,可以是String類型,自動(dòng)轉(zhuǎn)換的理解就是可以根據(jù)給定的值類型自動(dòng)選擇調(diào)用哪個(gè)方法來設(shè)置屬性。
- 自定義轉(zhuǎn)換:就是原始值不滿足屬性值的類型要求;舉兩個(gè)例子,backGround屬性需要Drawable,我們給@color/xxx肯性不行,還有一個(gè)較為常見的場(chǎng)景,我們一般拿到的時(shí)間戳,但是需要顯示的是xxxx年xx月xx日的格式。定義方法比較簡(jiǎn)單,就是在方法上加個(gè)@BindingConversion就可以了。咱們用個(gè)例子看下
// 我們有一個(gè)user對(duì)象 val user = User("二胖胖",18)
我們定義個(gè)轉(zhuǎn)義器<!-- 給text直接指定了對(duì)象了,理論上我們木有setText(user:User)類型的方法的 --> < android:text="@{viewModel.user}" />
看下最終表現(xiàn)@BindingConversion fun convertUserToString(user: User): String? { return user.toString() }
也就是編譯器會(huì)發(fā)現(xiàn)類型不匹配會(huì)找對(duì)應(yīng)的covert方法來用androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv9, org.geekbang.databindingtest.ViewExBindingAdaptersKt.convertUserToString(viewModelUser));
雙向綁定
- 首先這個(gè)東西看官網(wǎng)得看幾遍,個(gè)人覺得不是很好理解
- 其實(shí)掌握以下就可以了
- 這個(gè)的應(yīng)用場(chǎng)景就是,某個(gè)UI依賴一個(gè)數(shù)據(jù)展示,這個(gè)UI的內(nèi)容變化后會(huì)改變這個(gè)數(shù)值;例如EditText,我們初始值依賴一個(gè)變量value,隨著輸入的變化輸入框的值會(huì)實(shí)時(shí)改變value的值,翻譯過來就是通過簡(jiǎn)單的寫法可以實(shí)現(xiàn)如下兩個(gè)操作:
editText.text = value editText.addTextChangedListener(object : TextWatcher(){ override fun afterTextChanged(s: Editable?) { value = s.toString() } })
- 寫法的話就是語法糖@=
< android:text="@={value}" />
- 系統(tǒng)提供了大多數(shù)使用場(chǎng)景雙向特性
- 想自定義的話,需要掌握幾個(gè)注解用法 @BindingAdapter @InverseBindingAdapter @InverseBindingMethods
- 這個(gè)的應(yīng)用場(chǎng)景就是,某個(gè)UI依賴一個(gè)數(shù)據(jù)展示,這個(gè)UI的內(nèi)容變化后會(huì)改變這個(gè)數(shù)值;例如EditText,我們初始值依賴一個(gè)變量value,隨著輸入的變化輸入框的值會(huì)實(shí)時(shí)改變value的值,翻譯過來就是通過簡(jiǎn)單的寫法可以實(shí)現(xiàn)如下兩個(gè)操作:
ViewStub
- 額...沒咋用過...也就沒深入研究了,但是這個(gè)是布局優(yōu)化的一個(gè)點(diǎn),有興趣深究下就好
include
- 本質(zhì)只是一個(gè)DataBinding的包裹,掌握傳值就好了,不過這里寫演示實(shí)例時(shí)遇到個(gè)坑,就是在約束布局里的寬高用wrapper不行,了解下就好了。