DataBinding-使用篇

概念

DataBind 就是 基于apt技術(shù),幫我們生成了一些模板代碼,這些模板代碼大概解決了如下操作:

  1. 控件變量的聲明,類(lèi)似如下:
 @NonNull
 public final TextView tv1;//自動(dòng)解決了類(lèi)型匹配的問(wèn)題,不用擔(dān)心自己手抖觸發(fā)類(lèi)型轉(zhuǎn)換異常了
  1. 控件的查找賦值,相當(dāng)于自動(dòng)幫我們完成了類(lèi)似如下操作:
  tv1 = findViewById(R.id.tv1) //確保了自己腦子卡,忘記給聲明的變量賦值,引發(fā)空指針
  1. 控制的數(shù)據(jù)填充操作,也就是其本意數(shù)據(jù)綁定,類(lèi)似自動(dòng)完成了如下操作:
tv1.setText(user.getName())

讀前須知

  1. 官網(wǎng)連接:數(shù)據(jù)綁定庫(kù)
  2. 本文只講使用層面的對(duì)應(yīng)解析,不涉及原理流程之類(lèi),這點(diǎn)將在下一篇完善
  3. 本文主要基于官網(wǎng)的使用實(shí)例,集合kapt生成的相關(guān)代碼,來(lái)吃透用法背后的真實(shí)面紗(源碼)
  4. 本文不涉及配置,基礎(chǔ)引用等,需要有一定的DataBinding使用經(jīng)驗(yàn),但是只用但是不知道為啥這么用的大佬

按照官網(wǎng)的順序一點(diǎn)一點(diǎn)來(lái)

最常見(jià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庫(kù)給我們提供的一個(gè)數(shù)據(jù)綁定適配器
//這個(gè)方法翻譯過(guò)來(lái)就是接管了android:text的屬性的賦值邏輯,也就是當(dāng)遇到上面寫(xiě)法時(shí),會(huì)通過(guò)下面的靜態(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);引起的過(guò)渡繪制,很值得我們學(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í)還有很多,但是官方并不太建議在布局寫(xiě)太復(fù)雜的表達(dá)式,這樣會(huì)搞的布局文件很亂,按照J(rèn)etpack的整體思路,數(shù)據(jù)的邏輯處理,應(yīng)該的ViewModel中處理,布局里最好是取最終值就好,這里吐槽一點(diǎn),有些說(shuō)法是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è)咋沒(méi)適配器呢。因?yàn)镈atabind,針對(duì)屬性有對(duì)應(yīng)setXXX方法會(huì)默認(rèn)調(diào)用其setXXX方法就好,無(wú)需提供Adapter
//那android:text,也有setText()呀,為啥上面有呢,因?yàn)楣俜接X(jué)得那個(gè)方法太簡(jiǎn)單了,所以給提供了更優(yōu)化的方法,以達(dá)到優(yōu)化目的
//也就是說(shuō)默認(rèn)的會(huì)調(diào)用屬性對(duì)應(yīng)的setXXX方法,如果有適配器定制的話(huà)就調(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è)語(yǔ)法糖
viewModelNameJavaLangObjectNullViewModelLastNameViewModelName = ((viewModelNameJavaLangObjectNull) ? (viewModelLastName) : (viewModelName));
//給View填充數(shù)據(jù),講過(guò)了不啰嗦
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)入,主語(yǔ)xml里不支持‘<’符號(hào),要用&lt代替 -->
<import type="java.util.List"/>
<variable
           name="listdata"
           type="List&lt;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基類(lèi)提供的方法,與之對(duì)應(yīng)還有其它集合類(lèi)方法,用于獲取集合中某個(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)單的說(shuō)下使用,場(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='@{"寫(xiě)死的值"}' />
<!-- 外雙內(nèi)單 -->
< android:text="@{map[`firstName`]}" />
//到了編譯后就是用的死值,不會(huì)為其生成變量
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv5, "寫(xiě)死的值");
資源

主要是資源文件的動(dòng)態(tài)話(huà)

<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)聽(tīng)器方法簽名的方法。當(dāng)表達(dá)式求值結(jié)果為方法引用時(shí),數(shù)據(jù)綁定會(huì)將方法引用和所有者對(duì)象封裝到監(jiān)聽(tīng)器中,并在目標(biāo)視圖上設(shè)置該監(jiān)聽(tīng)器。如果表達(dá)式的求值結(jié)果為 null,則數(shù)據(jù)綁定不會(huì)創(chuàng)建監(jiān)聽(tīng)器,而是設(shè)置 null 監(jiān)聽(tīng)器。
  • 事件可以直接綁定到處理腳本方法,類(lèi)似于為 Activity 中的方法指定 android:onClick 的方式。與 ViewonClick` 特性相比,一個(gè)主要優(yōu)點(diǎn)是表達(dá)式在編譯時(shí)進(jìn)行處理,因此,如果該方法不存在或其簽名不正確,則會(huì)收到編譯時(shí)錯(cuò)誤。
  • 方法引用和監(jiān)聽(tīng)器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽(tīng)器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的。如果您希望在事件發(fā)生時(shí)對(duì)表達(dá)式求值,則應(yīng)使用監(jiān)聽(tīng)器綁定。

好難懂是吧,那我們接下來(lái)就根據(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ù)類(lèi)型一致,方法名隨意,對(duì)應(yīng)到官方的一句話(huà)就是”引用符合監(jiān)聽(tīng)器方法簽名的方法“

<variable
            name="clickHandler"
            type="org.geekbang.databindingtest.ListenerHandler" />

來(lái)個(gè)錯(cuò)誤的示范
fun tvOnClick(view: View) --> fun tvOnClick(context: Context);也就是方法簽名搞錯(cuò)了,會(huì)咋樣呢



會(huì)直接有個(gè)紅線(xiàn),提示錯(cuò)了,也就是不和規(guī)范在編譯器就不行了
修正過(guò)來(lái)的寫(xiě)法

android:onClick="@{clickHandler::tvOnClick}"

那么最終編譯出來(lái)的相關(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è)類(lèi)能解釋好多官方解釋

  1. 這個(gè)類(lèi)和實(shí)例是編譯時(shí)就創(chuàng)建,對(duì)應(yīng)官方的話(huà):方法引用和監(jiān)聽(tīng)器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽(tīng)器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的
  2. 這個(gè)類(lèi)里持有一個(gè)變量org.geekbang.databindingtest.ListenerHandler value,這個(gè)變量的類(lèi)型是我們的自定義的實(shí)現(xiàn)的類(lèi)型,值也很明顯就是我們聲明的那個(gè)clickHandler;這里對(duì)應(yīng)官方的話(huà):數(shù)據(jù)綁定會(huì)將方法引用和所有者對(duì)象封裝到監(jiān)聽(tīng)器中
  3. 接口的實(shí)現(xiàn)最終調(diào)用的是value對(duì)應(yīng)的方法;這也就能解釋通為啥定義的方法一定要符合監(jiān)聽(tīng)器的方法簽名了,也就是參數(shù)上要對(duì)應(yīng)好,從這里我們可以發(fā)現(xiàn),其實(shí)不一定參數(shù)類(lèi)型完全一致,只要是事件方法參數(shù)的子類(lèi)類(lèi)型就可以了,不過(guò)一般設(shè)置接口時(shí)就會(huì)根據(jù)依賴(lài)倒置規(guī)則確定了類(lèi)型上不能再具體了。

最后肯定是設(shè)置值了,這一些列操作編譯時(shí)相當(dāng)于都把代碼給我們寫(xiě)好了,至此所謂的方法引用方式綁定事件處理就通了

this.tv1.setOnClickListener(clickHandlerTvOnClickAndroidViewViewOnClickListener);
監(jiān)聽(tīng)器綁定
也是事件處理的一種方式,官方的解釋來(lái)一波:
  • 這些是在事件發(fā)生時(shí)進(jìn)行求值的 lambda 表達(dá)式。數(shù)據(jù)綁定始終會(huì)創(chuàng)建一個(gè)要在視圖上設(shè)置的監(jiān)聽(tīng)器。事件被分派后,監(jiān)聽(tīng)器會(huì)對(duì) lambda 表達(dá)式進(jìn)行求值。
  • 監(jiān)聽(tīng)器綁定是在事件發(fā)生時(shí)運(yùn)行的綁定表達(dá)式。它們類(lèi)似于方法引用,但允許您運(yùn)行任意數(shù)據(jù)綁定表達(dá)式。
  • 在方法引用中,方法的參數(shù)必須與事件監(jiān)聽(tīng)器的參數(shù)匹配。在監(jiān)聽(tīng)器綁定中,只有您的返回值必須與監(jiān)聽(tīng)器的預(yù)期返回值相匹配(預(yù)期返回值無(wú)效除外)

定義,聲明,使用

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)聽(tīng),這里的callBack肯定是一個(gè)OnClickListener
this.tv2.setOnClickListener(mCallback1);
// 果不其然,直接就是
@Nullable
 private final android.view.View.OnClickListener mCallback1;
// 那他賦值是誰(shuí)呢,看下面,這個(gè)有兩個(gè)參數(shù),傳了this->ActivityMainBinding,還有一個(gè)1?,這個(gè)1是干啥的。?!,F(xiàn)在不清楚
mCallback1 = new org.geekbang.databindingtest.generated.callback.OnClickListener(this, 1);
//看看這個(gè)類(lèi)的實(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);
    }
}
接下來(lái)就看那個(gè)接口的實(shí)現(xiàn)在哪里,通過(guò)開(kāi)始的賦值也能才到,實(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í)可以看出來(lái)就是各種非空邏輯的判斷,要確保不出現(xiàn)空指針
               //這里最終調(diào)用了我們定義的方法
                clickHandler.onClickByInfo(callbackArg_0, viewModelName);
            }
        }
    }

從上面源碼看,相比方法引用其實(shí)就是換了下寫(xiě)法,同時(shí)支持非簽名參數(shù)了而已,因?yàn)檫@些代碼也都是編譯時(shí)都設(shè)置好了。
那么這兩種看都是將原來(lái)的onClick的具體實(shí)現(xiàn)最終轉(zhuǎn)給了我們自己寫(xiě)的業(yè)務(wù)塊,只是方法參數(shù)上監(jiān)聽(tīng)器比方法引用更加靈活,這里可以推斷出場(chǎng)景選擇,點(diǎn)擊事件不依賴(lài)于數(shù)據(jù)邏輯時(shí)用方法引用就好,如果依賴(lài)于數(shù)據(jù),比如我們的Rv的Item里的點(diǎn)擊需要把Item的data帶出去,就可以用監(jiān)聽(tīng)器引用了。

可觀(guān)察數(shù)據(jù)對(duì)象

這個(gè)其實(shí)在實(shí)際中都以L(fǎng)iveData代替了,這最終是如何運(yùn)行的,且看下回分解。我們就通過(guò)一個(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;
//值肯定通過(guò)LiveData獲取的
if (viewModelDescriptionInfo != null) {
    viewModelDescriptionInfoGetValue = viewModelDescriptionInfo.getValue();
}
// LiveData通過(guò)變量賦值
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ò)關(guān)聯(lián)判斷,合理的去給LiveData去更新下觀(guān)察者,就不展開(kāi)了
            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è)觀(guān)察者
private static class LiveDataListener implements Observer,
            ObservableReference<LiveData<?>> {
        //一個(gè)監(jiān)聽(tīng)相關(guān)的數(shù)據(jù)包裝類(lèi),將各個(gè)參數(shù)存到這里面了
        final WeakListener<LiveData<?>> mListener;
            //略...
            mListener = new WeakListener(binder, localFieldId, this, referenceQueue);
           //略...
           //會(huì)給LiveData設(shè)置監(jiān)聽(tīng)
           liveData.observe(newOwner, this);
           //略...
        //響應(yīng)監(jiān)聽(tīng)變化
        public void onChanged(@Nullable Object o) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder != null) {
                binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
            }
        }
    }

從上線(xiàn)我們能看到當(dāng)生性周期狀態(tài)變化后,會(huì)交由ViewDataBinding的handleFieldChange去響應(yīng)變化,接下來(lái)我們跟一下這條線(xiàn)的主要代碼

 //如果字段變化后會(huì)走requestRebind
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
 //requestRebind是請(qǐng)求重新綁定的意思,最終繞來(lái)繞去會(huì)走到executeBindings();而executeBindings是執(zhí)行綁定的意思,最終會(huì)回到我們開(kāi)始的賦值階段

從上面看,我們的LiveData數(shù)據(jù)的管理還是有點(diǎn)復(fù)雜的,主要是很多預(yù)制的東西,本質(zhì)上還是注冊(cè)觀(guān)察者和響應(yīng)觀(guān)察者而已,只是將這一套流程模板化了而已

綁定適配器
自己的理解
  • 這個(gè)官網(wǎng)講述的都已經(jīng)很詳細(xì)了,這里主要是做做總結(jié),加深下理解,沒(méi)興趣的可以直接看官網(wǎng)。
  • 綁定的實(shí)質(zhì)是設(shè)置控件的某個(gè)屬性,如setText是改變文字,setAlpha是改變透明度,那么要改變這個(gè)屬性可能需要一些邏輯去定制,比如setText,如果新舊值沒(méi)有變化就沒(méi)必要設(shè)置了,多刷新一次而已,那么適配器的本質(zhì)作用就是為設(shè)置屬性提供自定義實(shí)現(xiàn)方式,類(lèi)似于RecycleView中我們將布局填充交給Adapter一樣,這里是我們將某個(gè)屬性的修改交給了一個(gè)Adapter而已。
適配器的定制原則
  1. 理論上任何屬性都可以定制
  2. 自動(dòng)選擇:有setter方法的都可以直接搞
    • 有屬性有對(duì)應(yīng)的setter方法,可以直接用;例如android:gravity="@{vm.gravity}"
    • 沒(méi)有屬性,但是有setter方法,可以通過(guò)app:xxx="xxx"方式使用;例如app:scrimColor="@{@color/scrim}"
  3. 指定自定義方法名稱(chēng):有屬性,但是沒(méi)有對(duì)應(yīng)的setter,但是呢有其它名稱(chēng)的方法可以單獨(dú)修改這個(gè)屬性,可以將屬性及方法建立關(guān)聯(lián)即可。例如android:tint這個(gè)屬性,木有setTint方法,但是有setImageTintList方法是用來(lái)設(shè)置各個(gè)屬性的,我們通過(guò)一下方法進(jìn)行關(guān)鍵即可。示例:
    @BindingMethods(value = [
            BindingMethod(
                type = android.widget.ImageView::class,
                attribute = "android:tint",
                method = "setImageTintList")])
    
    其實(shí)這個(gè)有,但是很少,首先一般寫(xiě)法都是用對(duì)應(yīng)setter方法,及時(shí)需要關(guān)聯(lián)的,大多數(shù)DataBinding框架已經(jīng)幫我們建立關(guān)聯(lián)了,發(fā)現(xiàn)木有時(shí)到androidx.databinding.adapters下找找看。
  4. 完全自定義:這個(gè)就是既沒(méi)有默認(rèn),也無(wú)法1v1關(guān)聯(lián)的場(chǎng)景了;例如我們有android:paddingLeft屬性,但沒(méi)有該屬性的sePaddingLeft,有改變的方法setPadding但這個(gè)哥們是四個(gè)參數(shù)是改四個(gè)padding值用的,也就是完全驢唇對(duì)不上馬嘴,就只能自定義了,這也是我們大多數(shù)場(chǎng)景了。
    • 這個(gè)主要是單參數(shù),組合參數(shù)的情況,官網(wǎng)給了明確的示例,并且官方庫(kù)也提供了很多封裝,比著葫蘆畫(huà)瓢就好。注意在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í)需要確保與屬性的值類(lèi)型一致,如我們gravity屬性需要一個(gè)int,我們給一個(gè)字符串類(lèi)型的肯定不合適。但是某些邏輯下我們得到的可能并不是int類(lèi)型,這就需要轉(zhuǎn)換了。轉(zhuǎn)換的目的是讓值能符合屬性設(shè)置的類(lèi)型要求。
  • 自動(dòng)轉(zhuǎn)換:有些屬性可以支持多種類(lèi)型,比如setText,可以是int的resId,可以是String類(lèi)型,自動(dòng)轉(zhuǎn)換的理解就是可以根據(jù)給定的值類(lèi)型自動(dòng)選擇調(diào)用哪個(gè)方法來(lái)設(shè)置屬性。
  • 自定義轉(zhuǎn)換:就是原始值不滿(mǎn)足屬性值的類(lèi)型要求;舉兩個(gè)例子,backGround屬性需要Drawable,我們給@color/xxx肯性不行,還有一個(gè)較為常見(jiàn)的場(chǎng)景,我們一般拿到的時(shí)間戳,但是需要顯示的是xxxx年xx月xx日的格式。定義方法比較簡(jiǎn)單,就是在方法上加個(gè)@BindingConversion就可以了。咱們用個(gè)例子看下
    // 我們有一個(gè)user對(duì)象
    val user = User("二胖胖",18)
    
    <!-- 給text直接指定了對(duì)象了,理論上我們木有setText(user:User)類(lèi)型的方法的 -->
    < android:text="@{viewModel.user}" />
    
    我們定義個(gè)轉(zhuǎn)義器
    @BindingConversion
    fun convertUserToString(user: User): String? {
        return user.toString()
    }
    
    看下最終表現(xiàn)
    androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv9, org.geekbang.databindingtest.ViewExBindingAdaptersKt.convertUserToString(viewModelUser));
    
    也就是編譯器會(huì)發(fā)現(xiàn)類(lèi)型不匹配會(huì)找對(duì)應(yīng)的covert方法來(lái)用
雙向綁定
  • 首先這個(gè)東西看官網(wǎng)得看幾遍,個(gè)人覺(jué)得不是很好理解
  • 其實(shí)掌握以下就可以了
    1. 這個(gè)的應(yīng)用場(chǎng)景就是,某個(gè)UI依賴(lài)一個(gè)數(shù)據(jù)展示,這個(gè)UI的內(nèi)容變化后會(huì)改變這個(gè)數(shù)值;例如EditText,我們初始值依賴(lài)一個(gè)變量value,隨著輸入的變化輸入框的值會(huì)實(shí)時(shí)改變value的值,翻譯過(guò)來(lái)就是通過(guò)簡(jiǎn)單的寫(xiě)法可以實(shí)現(xiàn)如下兩個(gè)操作:
       editText.text = value
       editText.addTextChangedListener(object : TextWatcher(){
            override fun afterTextChanged(s: Editable?) {
                value = s.toString()
            }
        })
      
    2. 寫(xiě)法的話(huà)就是語(yǔ)法糖@=
      < android:text="@={value}" />
      
    3. 系統(tǒng)提供了大多數(shù)使用場(chǎng)景雙向特性
    4. 想自定義的話(huà),需要掌握幾個(gè)注解用法 @BindingAdapter @InverseBindingAdapter @InverseBindingMethods
ViewStub
  • 額...沒(méi)咋用過(guò)...也就沒(méi)深入研究了,但是這個(gè)是布局優(yōu)化的一個(gè)點(diǎn),有興趣深究下就好
include
  • 本質(zhì)只是一個(gè)DataBinding的包裹,掌握傳值就好了,不過(guò)這里寫(xiě)演示實(shí)例時(shí)遇到個(gè)坑,就是在約束布局里的寬高用wrapper不行,了解下就好了。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容