DataBinding最全使用說(shuō)明

DataBinding最全使用說(shuō)明

Google開(kāi)源的數(shù)據(jù)綁定框架, 實(shí)現(xiàn)了MVVM架構(gòu), 增強(qiáng)了xml的功能, 大幅度精簡(jiǎn)了java代碼量, 并且代碼可讀性更高, 對(duì)性能的影響基本為零.

DataBinding會(huì)自動(dòng)在build目錄下生成類. 因?yàn)楸患蛇M(jìn)AndroidStudio所以不需要你手動(dòng)編譯會(huì)實(shí)時(shí)編譯, 并且支持大部分代碼補(bǔ)全.

啟用DataBinding

android{
      dataBinding {
        enabled = true;
    }
}
復(fù)制代碼

因?yàn)榕履銈儧](méi)注意到我寫在文章開(kāi)頭

  • 我想強(qiáng)調(diào)的是XML只做賦值或者簡(jiǎn)單的三元運(yùn)算或者判空等不要做復(fù)雜運(yùn)算;
  • 邏輯運(yùn)算在Model中
  • 有時(shí)候可以偷懶將Activity當(dāng)作ViewModel來(lái)使用

DataBinding的強(qiáng)大是毋庸置疑, 只會(huì)更方便(拋棄MVP吧);

鑒于文章篇幅, 后面我將會(huì)出一篇文章以及開(kāi)源庫(kù)告訴大家如何實(shí)現(xiàn)DataBinding是如何讓RecyclerView一行代碼寫通用適配器(無(wú)需寫實(shí)現(xiàn)類)

一行代碼實(shí)現(xiàn)多類型/添加頭布局腳布局/點(diǎn)擊事件;

[圖片上傳失敗...(image-313607-1554879196408)]

布局

布局文件

<layout>

    <data>
        <variable
            name="user"
            type="com.liangjingkanji.databinding.pojo.UserBean"/>
    </data>

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.liangjingkanji.databinding.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.userName}"
            />

    </RelativeLayout>

</layout>
復(fù)制代碼

layout

布局根節(jié)點(diǎn)必須是<layout> . 同時(shí)layout只能包含一個(gè)View標(biāo)簽. 不能直接包含<merge>

data

<data>標(biāo)簽的內(nèi)容即DataBinding的數(shù)據(jù). data標(biāo)簽只能存在一個(gè).

variable

通過(guò)<variable>標(biāo)簽可以指定類, 然后在控件的屬性值中就可以使用

<data>
    <variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
復(fù)制代碼

通過(guò)DataBinding的setxx()方法可以給Variable設(shè)置數(shù)據(jù). name值不能包含_下劃線

import

第二種寫法(導(dǎo)入), 默認(rèn)導(dǎo)入了java/lang包下的類(String/Integer). 可以直接使用被導(dǎo)入的類的靜態(tài)方法.

<data>
  <!--導(dǎo)入類-->
    <import type="com.liangfeizc.databindingsamples.basic.User" />
  <!--因?yàn)閁ser已經(jīng)導(dǎo)入, 所以可以簡(jiǎn)寫類名-->
    <variable name="user" type="User" />
</data>
復(fù)制代碼

使用類

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.userName}"
          />
<!--user就是在Variable標(biāo)簽中的name, 可以隨意自定義, 然后就會(huì)使用type中的類-->
復(fù)制代碼

Tip: user代表UserBean這個(gè)類, 可以使用UserBean中的方法以及成員變量. 如果是getxx()會(huì)自動(dòng)識(shí)別為xx. 注意不能使用字符串android, 否則會(huì)報(bào)錯(cuò)無(wú)法綁定.

class

<data>標(biāo)簽有個(gè)屬性<class>可以自定義DataBinding生成的類名以及路徑

<!--自定義類名-->
<data class="CustomDataBinding"></data>

<!--自定義生成路徑以及類型-->
<data class=".CustomDataBinding"></data> <!--自動(dòng)在包名下生成包以及類-->
復(fù)制代碼

Tip:注意沒(méi)有代碼自動(dòng)補(bǔ)全. 自定義路徑Module/build/generated/source/apt/debug/databinding/目錄下, 基本上不需要自定義路徑

默認(rèn):

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // ActivityMainBinding這個(gè)類根據(jù)布局文件名生成(id+Binding)
    ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    UserBean userBean = new UserBean();
    userBean.setUserName("姜濤");

    // setUser這個(gè)方法根據(jù)Variable標(biāo)簽的name屬性自動(dòng)生成
    viewDataBinding.setUser(userBean);
  }
}
復(fù)制代碼

alias

<variable>標(biāo)簽如果需要導(dǎo)入(import)兩個(gè)同名的類時(shí)可以使用alias屬性(別名屬性)

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
復(fù)制代碼

include

在include其他布局的時(shí)候可能需要傳遞變量(variable)值過(guò)去

<variable
          name="userName"
          type="String"/>

....

<include
         layout="@layout/include_demo"
         bind:userName="@{userName}"/>
復(fù)制代碼

include_demo

    <data>

        <variable
            name="userName"
            type="String"/>
    </data>

...

android:text="@{userName}"
復(fù)制代碼

兩個(gè)布局通過(guò)includebind:<變量名>值來(lái)傳遞. 而且兩者必須有同一個(gè)變量

DataBinding不支持merge標(biāo)簽

自動(dòng)布局屬性

DataBinding對(duì)于自定義屬性支持非常好, 只要View中包含setter方法就可以直接在布局中使用該屬性

public void setCustomName(@NonNull final String customName) {
    mLastName.setText("吳彥祖");
  }
復(fù)制代碼

然后直接使用(但是IDE沒(méi)有代碼補(bǔ)全)

app:customName="@{@string/wuyanzu}"
復(fù)制代碼

但是setter方法只支持單個(gè)參數(shù). app:這個(gè)命名空間可以隨意

數(shù)據(jù)雙向綁定

視圖跟隨數(shù)據(jù)刷新

BaseObservable

如果需要數(shù)據(jù)變化是視圖也跟著變化則需要使用到以下兩種方法

有兩種方式:

  1. 繼承BaseObservable

    public class ObservableUser extends BaseObservable {
        private String firstName;
        private String lastName;
    
        @Bindable
        public String getFirstName() {
            return firstName;
        }
    
      // 注解才會(huì)自動(dòng)在build目錄BR類中生成entry, 要求方法名必須以get開(kāi)頭
        @Bindable
        public String getLastName() {
            return lastName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
            notifyPropertyChanged(BR.firstName);
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
            notifyPropertyChanged(BR.lastName); // 需要手動(dòng)刷新
        }
    }
    復(fù)制代碼
    

還可以監(jiān)聽(tīng)屬性改變事件

ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
  @Override
  public void onPropertyChanged(Observable observable, int i) {

  }
});
復(fù)制代碼

屬性第一次改變時(shí)會(huì)回調(diào)兩次, 之后都只回調(diào)一次. 如果使用notifyChange()不會(huì)得到id(即i等于0). 使用

notifyPropertyChanged(i)就可以在回調(diào)里面得到id.

BaseObservable和Observable的區(qū)別:

  1. BaseObservable是實(shí)現(xiàn)了Observable的類, 幫我們實(shí)現(xiàn)了監(jiān)聽(tīng)器的線程安全問(wèn)題.
  2. BaseObservable使用了PropertyChangeRegistry來(lái)執(zhí)行OnPropertyChangedCallback
  3. 所以我不推薦你直接實(shí)現(xiàn)Observable.

ObservableField

databinding默認(rèn)實(shí)現(xiàn)了一系列實(shí)現(xiàn)Observable接口的字段類型

BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
復(fù)制代碼

示例

public class PlainUser {
  public final ObservableField<String> firstName = new ObservableField<>();
  public final ObservableField<String> lastName = new ObservableField<>();
  public final ObservableInt age = new ObservableInt();
}
復(fù)制代碼

對(duì)于集合數(shù)據(jù)類型ObservableArrayMap/ObservableArrayLis/ObjservableMap等集合數(shù)據(jù)類型

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
復(fù)制代碼

使用

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
復(fù)制代碼

Tip:

  1. 還支持ObservableParcelable<Object>序列化數(shù)據(jù)類型
  2. 上面說(shuō)的這兩種只會(huì)視圖跟隨數(shù)據(jù)更新, 數(shù)據(jù)并不會(huì)跟隨視圖刷新.
  3. ObservableField同樣支持addOnPropertyChangedCallback監(jiān)聽(tīng)屬性改變

數(shù)據(jù)跟隨視圖刷新

通過(guò)表達(dá)式使用@=表達(dá)式就可以視圖刷新的時(shí)候自動(dòng)更新數(shù)據(jù), 但是要求數(shù)據(jù)實(shí)現(xiàn)以下兩種方式修改才會(huì)觸發(fā)刷新

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textNoSuggestions"
    android:text="@={model.name}"/>
復(fù)制代碼

這種雙向綁定存在一個(gè)很大的問(wèn)題就是會(huì)死循環(huán). 數(shù)據(jù)變化(回調(diào)監(jiān)聽(tīng)器)觸發(fā)視圖變化, 然后視圖又會(huì)觸發(fā)數(shù)據(jù)變化(再次回調(diào)監(jiān)聽(tīng)器), 然后一直循環(huán), 設(shè)置相同的數(shù)據(jù)也視為數(shù)據(jù)變化.

所以我們需要判斷當(dāng)前變化的數(shù)據(jù)是否等同于舊數(shù)據(jù)

public class CustomBindingAdapter {

  @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
    CharSequence oldText = view.getText();

    if (!haveContentsChanged(text, oldText)) {
      return; // 數(shù)據(jù)沒(méi)有變化不進(jìn)行刷新視圖
    }
    view.setText(text);
  }

  // 本工具類截取自官方源碼
  private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
    if ((str1 == null) != (str2 == null)) {
      return true;
    } else if (str1 == null) {
      return false;
    }
    final int length = str1.length();
    if (length != str2.length()) {
      return true;
    }
    for (int i = 0; i < length; i++) {
      if (str1.charAt(i) != str2.charAt(i)) {
        return true;
      }
    }
    return false;
  }
}
復(fù)制代碼

Tip:

  1. 根據(jù)我上面說(shuō)的, 監(jiān)聽(tīng)器至少回調(diào)兩次(數(shù)據(jù)->視圖, 視圖-> 數(shù)據(jù))

  2. 以下這種是無(wú)效的, 因?yàn)镾tring參數(shù)傳遞屬于引用類型變量并不是常量, 需要用equals()

    // 本段截取官方源碼, 我也不知道這sb為什么這么寫
    if (text == oldText || (text == null && oldText.length() == 0)) {
      return; 
    }
    
    /**/
    復(fù)制代碼
    

    正確

    if (text == null || text.equals(oldText) || oldText.length() == 0) {
      return;
    }
    復(fù)制代碼
    

總結(jié)就是如果沒(méi)有默認(rèn)實(shí)行的控件屬性使用雙向數(shù)據(jù)綁定 就需要你自己實(shí)現(xiàn)BindingAdapter注解

注解

@Bindable

用于數(shù)據(jù)更新自動(dòng)刷新視圖. 后面提.

@BindingAdapter

用于標(biāo)記方法. 前面提到了DataBinding自定義屬性自動(dòng)識(shí)別setter.

如果我們需要自定義xml, 就需要修改View的源碼 ,但是DataBinding還有第二種方法相當(dāng)于可以將setter方法抽取出來(lái), 并且同時(shí)支持多個(gè)屬性.

圖片加載框架可以方便使用此方法.

@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
  public static void loadImage(ImageView view, String url, Drawable error) {
    Glide.with(view.getContext()).load(url).into(view);
  }
復(fù)制代碼
  1. 修飾方法, 要求方法必須public static
  2. 方法參數(shù)第一個(gè)要求必須是View
  3. 方法名不作要求
  4. 最后這個(gè)boolean類型是可選參數(shù). 可以要求是否所有參數(shù)都需要填寫. 默認(rèn)true.
  5. 如果requireAll為false, 你沒(méi)有填寫的屬性值將為null. 所以需要做非空判斷.

使用:

<ImageView
           android:layout_width="match_parent"
           android:layout_height="200dp"
           app:error="@{@drawable/error}"
           wuyanzu:imageUrl="@{imageUrl}"
           app:onClickListener="@{activity.avatarClickListener}"
           />
復(fù)制代碼

可以看到命名空間可以隨意, 但是如果在BindingAdapter的數(shù)組內(nèi)你定義了命名空間就必須完全遵守

例如:

// 這里省略了一個(gè)注解參數(shù).   
@BindingAdapter({ "android:imageUrl", "error" })
  public static void loadImage(ImageView view, String url, Drawable error) {
    if(url == null) return;
    Glide.with(view.getContext()).load(url).into(view);
  }
復(fù)制代碼

Tip: 如果你的數(shù)據(jù)初始化是在異步的. 會(huì)回調(diào)方法但是數(shù)據(jù)為null(成員默認(rèn)值). 所以我們必須要首先進(jìn)行判空處理.

@BindingMethods

DataBinding默認(rèn)可以在布局中使用setter方法作為自定義屬性, 但是如果不是setter格式的方法就要使用BindingMethod注解了. 通過(guò)創(chuàng)建一個(gè)自定義屬性來(lái)關(guān)聯(lián)一個(gè)類中已有的方法.

該注解屬于一個(gè)容器. 內(nèi)部參數(shù)是一個(gè)@BindingMethod數(shù)組, 只能用于修飾類(任意類都可以, 類可以為空)

官方示例:

@BindingMethods({
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"),
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"),
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"),
})
public class ProgressBarBindingAdapter {
}
復(fù)制代碼

@BindingMethod

該注解必須有三個(gè)屬性

  1. type: 字節(jié)碼
  2. attribute: 屬性
  3. method: 方法

會(huì)在指定的字節(jié)碼(type)中尋找方法(method), 然后通過(guò)你創(chuàng)建的布局屬性(Attribute)來(lái)回調(diào)方法

如果屬性名和@BindingAdapter沖突會(huì)報(bào)錯(cuò)

Tip: 可以注意到該注解只是單純地關(guān)聯(lián)已有的方法, 并不能新增方法. 所以全都是注解的空類.

@BindingConversion

屬性值自動(dòng)進(jìn)行類型轉(zhuǎn)換

  1. 只能修飾public static方法.
  2. 任意位置任意方法名都不限制
  3. DataBinding自動(dòng)匹配被該注解修飾的方法和匹配參數(shù)類型
  4. 返回值類型必須和屬性setter方法匹配, 且參數(shù)只能有一個(gè)
  5. 要求屬性值必須是@{}DataBinding表達(dá)式

官方示例:

public class Converters {
    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
        return new ColorDrawable(color);
    }
    @BindingConversion
    public static ColorStateList convertColorToColorStateList(int color) {
        return ColorStateList.valueOf(color);
    }
}
復(fù)制代碼

設(shè)置布局中TextView的背景,

android:background="@{`吳彥祖`}"
復(fù)制代碼

可以看到我給背景隨意設(shè)置一個(gè)字符串. 這樣就不會(huì)匹配Background的int參數(shù)類型. 然后DataBinding就會(huì)檢索匹配該類型的@BindingConversion方法. 然后轉(zhuǎn)換.

注意android:text如果想用int自動(dòng)轉(zhuǎn)String是不可以的, 因?yàn)閕nt值會(huì)被識(shí)別為resource id. @BindingConversion無(wú)法工作.

@InverseMethod

在android studio3.0提供inverse系列的新注解, 全部都是針對(duì)數(shù)據(jù)雙向綁定.

在數(shù)據(jù)和視圖的數(shù)據(jù)不統(tǒng)一時(shí)可以使用該注解@InverseMethod解決數(shù)據(jù)轉(zhuǎn)換的問(wèn)題

例如數(shù)據(jù)模型存儲(chǔ)用戶的id但是視圖不顯示id而是顯示用戶名(數(shù)據(jù)和視圖的類型不一致), 我們就需要在兩者之間轉(zhuǎn)換.

需要?jiǎng)?chuàng)建public static兩個(gè)方法, 我們簡(jiǎn)稱為"轉(zhuǎn)換方法(convertion method)"和"反轉(zhuǎn)方法(inverse method)"

  • 轉(zhuǎn)換方法與反轉(zhuǎn)方法的參數(shù)數(shù)量必須相同
  • 轉(zhuǎn)換方法的最終參數(shù)的類型與反轉(zhuǎn)方法的返回值必須相同

轉(zhuǎn)換方法: 是刷新視圖的時(shí)候使用 (決定視圖顯示數(shù)據(jù)) 會(huì)回調(diào)兩次(文章后面詳細(xì)解釋雙向綁定的時(shí)候可以知道為何)

反轉(zhuǎn)方法: 是刷新數(shù)據(jù)的時(shí)候使用 (決定實(shí)體存儲(chǔ)數(shù)據(jù))

簡(jiǎn)單示例:

在用戶id和用戶名之間轉(zhuǎn)換. 存儲(chǔ)id但是顯示的時(shí)候顯示用戶名

  @InverseMethod("toID") public static String toName(TextView view, int id) {
    if (id == 1) {
      return "吳彥祖";
    }
    return "";
  }

  public static int toID(TextView view, String name) {
    if (name.equals("吳彥祖")) {
      return 1;
    }
    return 0;
  }
復(fù)制代碼

使用

    <TextView
        android:id="@+id/iv"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@={MyInverseMethod.toName( iv, data.id)}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />
復(fù)制代碼

注意和BindingAdapter不同, 參數(shù)有View表達(dá)式就必須加上View的id

Tip:

在這個(gè)注解之前其實(shí)都是通過(guò)修改實(shí)體的setter和getter方法達(dá)到類型的轉(zhuǎn)換. 但是這樣會(huì)侵入整個(gè)實(shí)體類

我使用的gson類都是自動(dòng)生成的我并不想去手動(dòng)修改任何方法.

@InverseBindingAdapter

參數(shù):

  • String attribute 屬性值(必填)
  • String event 非必填, 默認(rèn)值 屬性值 + AttrChanged后綴

介紹

  • 作用于方法,方法須為公共靜態(tài)方法。
  • 方法的第一個(gè)參數(shù)必須為View類型
  • 必須與@BindingAdapter配合使用

event: 這個(gè)屬性存在默認(rèn)值(上面提過(guò)默認(rèn)值的生成規(guī)則), 我們需要?jiǎng)?chuàng)建@BindingAdapter方法來(lái)實(shí)現(xiàn)event的屬性. 這個(gè)方法我暫且稱為數(shù)據(jù)變更方法.

在你綁定DataBinding時(shí)候回自動(dòng)調(diào)用這個(gè)數(shù)據(jù)變更方法, (這個(gè)數(shù)據(jù)變更方法創(chuàng)建的屬性并不能在xml中使用)

@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
  return view.getText().toString();
}
復(fù)制代碼

android:text屬性使用@={}雙向綁定表達(dá)式. 數(shù)據(jù)變化觸發(fā)視圖刷新是回調(diào)setter方法

數(shù)據(jù)變更方法(官方源碼簡(jiǎn)化版):

 @BindingAdapter(value = {
      "android:textAttr"
  }, requireAll = false)
  public static void setTextWatcher(TextView view, final InverseBindingListener textAttrChanged) {

    // 創(chuàng)建一個(gè)文字變化監(jiān)聽(tīng)器
    final TextWatcher newValue;

    // 如果全部為null不要監(jiān)聽(tīng)器
    if (textAttrChanged == null) {
      newValue = null;
    } else {
      newValue = new TextWatcher() {

        @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }
        @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
          if (textAttrChanged != null) {
            // 通知刷新
            textAttrChanged.onChange();
          }
        }

        @Override public void afterTextChanged(Editable s) {

        }
      };
    }
    // 如果視圖已經(jīng)有一個(gè)監(jiān)聽(tīng)器就先刪除
    final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
      view.removeTextChangedListener(oldValue);
    }

    // 給視圖添加監(jiān)聽(tīng)器
    if (newValue != null) {
      view.addTextChangedListener(newValue);
    }
  }
復(fù)制代碼

這里用到一個(gè)InverseBindingListener

public interface InverseBindingListener {
    /**
     * Notifies the data binding system that the attribute value has changed.
     */
    void onChange();
}
復(fù)制代碼

總結(jié)就是你只要通知

@InverseBindingMethods

類似BindingMethods. 參數(shù)是@InverseBindingMethod

如果說(shuō)BindingMethods是關(guān)聯(lián)setter方法和自定義屬性, 那么InverseBindingMethods就是關(guān)聯(lián)getter方法和自定義屬性.

setter是更新視圖的時(shí)候使用, 而getter方法是更新數(shù)據(jù)時(shí)候使用的

必須與@BindingAdapter配合使用

  • 修飾類

示例:

@InverseBindingMethods({
        @InverseBindingMethod(type = RadioGroup.class, attribute = "android:checkedButton", method = "getCheckedRadioButtonId"),
})
public class RadioGroupBindingAdapter {
    @BindingAdapter("android:checkedButton")
    public static void setCheckedButton(RadioGroup view, int id) {
        if (id != view.getCheckedRadioButtonId()) {
            view.check(id);
        }
    }
復(fù)制代碼

@InverseBindingMethod

參數(shù):

  • Class type 控件的字節(jié)碼

  • String attribute 屬性

  • String event 默認(rèn)值是屬性加AttrChanged后綴作為默認(rèn)值

  • String method Attribute值的getter形式作為默認(rèn)值通過(guò)屬性指定變化監(jiān)聽(tīng)和返回方法

在自動(dòng)生成DataBinding代碼中可以看到

    private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of data.name
            //         is data.setName((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv);  // 拿到變化的屬性
            // localize variables for thread safety
            // data != null
            boolean dataJavaLangObjectNull = false;
            // data.name
            java.lang.String dataName = null;
            // data
            com.liangjingkanji.databinding.Bean data = mData; // 拿到數(shù)據(jù)

            dataJavaLangObjectNull = (data) != (null);
            if (dataJavaLangObjectNull) {

                data.setName(((java.lang.String) (callbackArg_0))); // 存儲(chǔ)到數(shù)據(jù)
            }
        }
    };
復(fù)制代碼

所以如果你沒(méi)用重寫Inverse的數(shù)據(jù)變更方法將無(wú)法讓視圖通知數(shù)據(jù)刷新.

// 該方法會(huì)在綁定布局的時(shí)候回調(diào)
    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String dataName = null;
        com.liangjingkanji.databinding.Bean data = mData;

        if ((dirtyFlags & 0x1aL) != 0) {

                if (data != null) {
                    // read data.name
                    dataName = data.getName();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x1aL) != 0) {
            // api target 1

            com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
        }
        if ((dirtyFlags & 0x10L) != 0) {
            // api target 1

          // 重點(diǎn)是這段代碼, 將上面創(chuàng)建的監(jiān)聽(tīng)器傳入setTextWatcher方法
            com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr);
        }
    }
復(fù)制代碼

總結(jié)

@BindingBuildInfo@Untaggable這兩個(gè)注解是DataBinding自動(dòng)生成Java類時(shí)使用的.

  • Bindable

    設(shè)置數(shù)據(jù)刷新視圖. 自動(dòng)生成BR的ID

  • BindingAdapter

    設(shè)置自定義屬性. 可以覆蓋系統(tǒng)原有屬性

  • BindingMethod/BindingMethods

    關(guān)聯(lián)自定義屬性到控件原有的setter方法

  • BindingConversion

    如果屬性不能匹配類型參數(shù)將自動(dòng)根據(jù)類型參數(shù)匹配到該注解修飾的方法來(lái)轉(zhuǎn)換

  • InverseMethod

    負(fù)責(zé)實(shí)現(xiàn)視圖和數(shù)據(jù)之間的轉(zhuǎn)換

  • InverseBindingAdapter

    視圖通知數(shù)據(jù)刷新的

  • InverseBindingMethod/InverseBindingMethods

    視圖通知數(shù)據(jù)刷新的(如果存在已有g(shù)etter方法可用的情況下)

建議參考官方實(shí)現(xiàn)源碼:

DataBindingAdapter

表達(dá)式

@{}里面除了可以執(zhí)行方法以外還可以寫表達(dá)式, 并且支持一些特有表達(dá)式

  • 算術(shù) + - / * %
  • 字符串合并 +
  • 邏輯 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比較 == > < >= <=
  • Instanceof
  • Grouping ()
  • 文字 - character, String, numeric, null
  • Cast
  • 方法調(diào)用
  • Field 訪問(wèn)
  • Array 訪問(wèn) []
  • 三元 ?:

避免空指針

variable的值即使設(shè)置null或者沒(méi)有設(shè)置也不會(huì)出現(xiàn)空指針異常.

這是因?yàn)楣俜揭呀?jīng)用DataBinding的@BindingAdapter注解重寫了很多屬性. 并且里面進(jìn)行了判空處理.

<variable
    name="userName"
    type="String"/>

.....

android:text="@{userName}"
復(fù)制代碼

不會(huì)出現(xiàn)空指針異常.

dataBinding.setUserName(null);
復(fù)制代碼

并且還支持特有的非空多元表達(dá)式

android:text="@{user.displayName ?? user.lastName}"
復(fù)制代碼

就等價(jià)于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
復(fù)制代碼

還是需要注意數(shù)組越界的

集合

集合不屬于java.lang*下, 需要導(dǎo)入全路徑.

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map<String, String>"/>
復(fù)制代碼

上面這種寫法會(huì)報(bào)錯(cuò)

Error:與元素類型 "variable" 相關(guān)聯(lián)的 "type" 屬性值不能包含 '<' 字符。
復(fù)制代碼

因?yàn)?code><符號(hào)需要轉(zhuǎn)義.

常用轉(zhuǎn)義字符

? 空格 &nbsp; &#160;

< 小于號(hào) &lt; &#60;

大于號(hào) &gt; &#62;

& 與號(hào) &amp; &#38; " 引號(hào) &quot; &#34; ‘ 撇號(hào) &apos; &#39; × 乘號(hào) &times; &#215; ÷ 除號(hào) &divide; &#247;

正確寫法

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map&lt;String, String&gt;"/>
復(fù)制代碼

集合和數(shù)組都可以用[]來(lái)得到元素

android:text="@{map["firstName"]}"
復(fù)制代碼

字符串

如果想要在@{}中使用字符串, 可以使用三種方式

第一種:

android:text='@{"吳彥祖"}'
復(fù)制代碼

第二種:

android:text="@{`吳彥祖`}"
復(fù)制代碼

第三種:

android:text="@{@string/user_name}"
復(fù)制代碼

同樣支持@color或@drawable

格式化字符串

首先在strings中定義<string>

<string name="string_format">名字: %s  性別: %s</string>
復(fù)制代碼

然后就可以使用DataBinding表達(dá)式

android:text="@{@string/string_format(`吳彥祖`, `男`)}"
復(fù)制代碼

輸出內(nèi)容:

名字: 吳彥祖 性別: 男
復(fù)制代碼

默認(rèn)值

如果Variable還沒(méi)有復(fù)制就會(huì)使用默認(rèn)值顯示.

android:text="@{user.integral, default=`30`}"
復(fù)制代碼

上下文

DataBinding本身提供了一個(gè)名為context的Variable. 可以直接使用. 等同于View的getContext().

android:text="@{context.getApplicationInfo().toString()}"
復(fù)制代碼

引用其他控件

          <TextView
              android:id="@+id/datingName"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_marginLeft="8dp"
              android:layout_toRightOf="@id/iv_dating"
              android:text="活動(dòng)"
              />

/...
<TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_marginLeft="8dp"
              android:layout_toRightOf="@id/iv_order"
              android:text="@{datingName.text}"
              />
復(fù)制代碼

引用包含_的控件id是可以直接忽略該符號(hào). 例如tv_name直接寫tvName.

謝謝 lambda 指出錯(cuò)誤

不論順序都可以引用

使用Class

如果想用Class作為參數(shù)傳遞, 那么該Class不能直接通過(guò)靜態(tài)導(dǎo)入來(lái)使用. 需要作為字段常量來(lái)使用

事件綁定

事件綁定分為兩種:

  1. 方法引用
  2. 監(jiān)聽(tīng)綁定

對(duì)于默認(rèn)的事件需要書寫同樣的參數(shù)的方法才能接受到, 否則報(bào)錯(cuò). 例如onClick()方法必須有View參數(shù).

方法引用

public class MyHandlers {
  // 注意必須要傳View參數(shù)
    public void onClickFriend(View view) { ... }
}
復(fù)制代碼

直接通過(guò)View的屬性來(lái)調(diào)用類方法

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable
              name="activity"
              type="com.liangjingkanji.databinding.MainActivity"/>
  </data>

  <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <TextView android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@{user.firstName}"
              android:onClick="@{activit::click}"/>

  </LinearLayout>
</layout>
復(fù)制代碼

Tip: activity.clickactivity::click都屬于方法調(diào)用, 但是如果是activity.click()就會(huì)報(bào)錯(cuò). 因?yàn)閷?duì)于默認(rèn)事件需要統(tǒng)一參數(shù). 必須加上activity.click(View v)

監(jiān)聽(tīng)綁定

上面提到的都不能向回調(diào)里面?zhèn)鬟f自定義參數(shù). 而如果使用

android:onClick="@{()->activity.click(text)}"
復(fù)制代碼

就可以自定義回調(diào)參數(shù)了

ActivityMainBinding dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); 

dataBinding.setActivity(this);
dataBinding.setText("吳彥祖"); // 順序無(wú)所謂
復(fù)制代碼

然后在布局文件中使用Lambda

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable
              name="text"
              type="String"/>

    <variable
              name="activity"
              type="com.liangjingkanji.databinding.MainActivity"/>
  </data>
  <LinearLayout 
                android:layout_width="match_parent"                             
                android:layout_height="match_parent">
    <Button 
            android:layout_width="wrap_content"                                             android:layout_height="wrap_content"
            android:onClick="@{()->activity.click(text)}" />
  </LinearLayout>
</layout>
復(fù)制代碼

注意: DataBinding會(huì)將控件所有的setter方法全部暴露為xml屬性. 是不是很方便??

DataBinding組件

ViewDataBinding

自動(dòng)生成的DataBinding類都繼承自該類. 所以都擁有該類的方法

void    addOnRebindCallback(OnRebindCallback listener)
// 添加綁定監(jiān)聽(tīng)器, 可以在Variable被設(shè)置的時(shí)候回調(diào)

void    removeOnRebindCallback(OnRebindCallback listener)
// 刪除綁定監(jiān)聽(tīng)器

View    getRoot()
// 返回被綁定的視圖對(duì)象

abstract void   invalidateAll()
// 使所有的表達(dá)式無(wú)效并且立刻重新設(shè)置表達(dá)式. 會(huì)重新觸發(fā)OnRebindCallback回調(diào)(可以看做重置)

abstract boolean    setVariable(int variableId, Object value)
// 可以根據(jù)字段id來(lái)設(shè)置變量

void    unbind()
// 解綁布局, ui不會(huì)根據(jù)數(shù)據(jù)來(lái)變化, 但是監(jiān)聽(tīng)器還是會(huì)觸發(fā)的
復(fù)制代碼

這里有三個(gè)方法需要重點(diǎn)講解:

abstract boolean    hasPendingBindings()
// 當(dāng)ui需要根據(jù)當(dāng)前數(shù)據(jù)變化時(shí)就會(huì)返回true(數(shù)據(jù)變化后有一瞬間)

void    executePendingBindings()
// 強(qiáng)制ui立刻刷新數(shù)據(jù), 
復(fù)制代碼

當(dāng)你改變了數(shù)據(jù)以后(在你設(shè)置了Observable觀察器的情況下)會(huì)馬上刷新ui, 但是會(huì)在下一幀才會(huì)刷新UI, 存在一定的延遲時(shí)間. 在這段時(shí)間內(nèi)hasPendingBindings()會(huì)返回true. 如果想要同步(或者說(shuō)立刻)刷新UI可以馬上調(diào)用executePendingBindings().

OnRebindCallback:

該監(jiān)聽(tīng)器可以監(jiān)聽(tīng)到布局綁定的生命周期

    mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /**
       * 綁定之前
       * @param binding
       * @return 如果返回true就會(huì)綁定布局, 返回false則取消綁定
       */
      @Override public boolean onPreBind(ViewDataBinding binding) {
        return false;
      }

      /**
       * 如果取消綁定則回調(diào)該方法(取決于onPreBind的返回值)
       * @param binding
       */
      @Override public void onCanceled(ViewDataBinding binding) {
        super.onCanceled(binding);
      }

      /**
       * 綁定完成
       * @param binding
       */
      @Override public void onBound(ViewDataBinding binding) {
        super.onBound(binding);
      }
    });
復(fù)制代碼

DataBinding也有個(gè)數(shù)據(jù)變更監(jiān)聽(tīng)器, 可以監(jiān)聽(tīng)Variable的設(shè)置事件

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

  /**
       * 會(huì)在DataBinding設(shè)置數(shù)據(jù)的時(shí)候回調(diào)
       * @param sender DataBinding生成的類
       * @param propertyId Variable的id
       */
  @Override public void onPropertyChanged(Observable sender, int propertyId) {
    ActivityMainBinding databinding = (ActivityMainBinding) sender;
    switch (propertyId) {
      case BR.data:
        Log.d("日志", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
        break;
      case BR.dataSecond:

        break;
    }
  }
});
復(fù)制代碼

DataBindingUtil

DataBinding不僅可以綁定Activity還可以綁定視圖內(nèi)容(View)


// 視圖
static <T extends ViewDataBinding> T    bind(View root)

static <T extends ViewDataBinding> T    bind(View root, 
                                             DataBindingComponent bindingComponent)

// 布局
static <T extends ViewDataBinding> T    inflate(LayoutInflater inflater, 
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent, DataBindingComponent bindingComponent) // 組件

static <T extends ViewDataBinding> T    inflate(LayoutInflater inflater,
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent)

// activity
static <T extends ViewDataBinding> T    setContentView(Activity activity, 
                                                       int layoutId)

static <T extends ViewDataBinding> T    setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
復(fù)制代碼

還有兩個(gè)不常用的方法, 檢索視圖是否被綁定, 如果沒(méi)有綁定返回nul

static <T extends ViewDataBinding> T    getBinding(View view)

// 和getBinding不同的是如果視圖沒(méi)有綁定會(huì)去檢查父容器是否被綁定
static <T extends ViewDataBinding> T    findBinding(View view)
復(fù)制代碼

其他的方法

// 根據(jù)傳的BR的id來(lái)返回字符串類型. 可能用于日志輸出
static String   convertBrIdToString(int id)
復(fù)制代碼

例如BR.name這個(gè)字段對(duì)應(yīng)的是4, 就可以使用該方法將4轉(zhuǎn)成"name"

DataBindingComponent

每個(gè)DataBinding都可以擁有一個(gè)組件或者說(shuō)設(shè)置一個(gè)默認(rèn)的全局組件

創(chuàng)建一個(gè)Component的步驟:

  1. 創(chuàng)建一個(gè)MyDataBindingComponent實(shí)現(xiàn)接口DataBindingComponent
  2. 創(chuàng)建MyBindingAdapter類, 用@BindingAdapter修飾其成員方法(不需要靜態(tài))
  3. 在MyDataBindingComponent寫入一個(gè)get**方法()來(lái)返回該MyBindingAdapter
public class MyDefaultComponent implements DataBindingComponent {

  public MyBindingAdapter mAdapter = new MyBindingAdapter();

  public MyBindingAdapter getMyBindingAdapter() {
    return mAdapter;
  }

  class MyBindingAdapter {
    @BindingAdapter("android:text") public void setText(TextView textView, String text) {
    /*省略*/
      textView.setText(text);
    }
  }
}
復(fù)制代碼

設(shè)置默認(rèn)組件都是由DataBindingUtils設(shè)置, 但是方法也有所不同

static DataBindingComponent getDefaultComponent()

static void setDefaultComponent(DataBindingComponent bindingComponent)
復(fù)制代碼

以上這種設(shè)置必須在綁定視圖之前設(shè)置, 并且是默認(rèn)全局的, 只需要設(shè)置一次.

static <T extends ViewDataBinding> T    setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
復(fù)制代碼

類似于上面這種在綁定視圖的同時(shí)來(lái)設(shè)置組件需要每次綁定視圖都設(shè)置, 否則就會(huì)報(bào)錯(cuò).

或者你可以將@BindingAdapter注解的方法變?yōu)镾tatic修飾.

另外不僅僅是@BindingAdapter可以設(shè)置成組件, @InverseBindingAdapter同樣可以

注意

  1. 可以使用include不過(guò)不能作為root布局. merge不能使用
  2. 如果沒(méi)有自動(dòng)生成DataBinding類可以先寫個(gè)variable(或者make module下)

推薦插件

關(guān)于DataBinding我推薦使用插件生成, 方便快捷很多;

DataBindingModelFormatter

快捷生成實(shí)現(xiàn)Observable的數(shù)據(jù)模型

DataBindingSupport

自動(dòng)生成DataBinding所需的XML格式

?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • DataBinding 庫(kù)是 Google 公司 Android Framework UI 工具團(tuán)隊(duì)開(kāi)發(fā)出來(lái)的一款...
    bravian閱讀 5,511評(píng)論 2 16
  • 作者: weiyf時(shí)間: 2016-10-31 21:35:33原文鏈接:https://developer.an...
    衛(wèi)裕發(fā)閱讀 4,849評(píng)論 2 15
  • 一個(gè)剛?cè)胄邪肽甑牟锁B(niǎo)安卓開(kāi)發(fā)人員,始終有一顆不安分的心。mvvm框架是我在學(xué)習(xí)vue的時(shí)候才知道的一種新型架構(gòu)。公...
    sakasa閱讀 4,354評(píng)論 5 22
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,619評(píng)論 1 32
  • 孩子,也許你現(xiàn)在會(huì)很高興,自己終于小學(xué)畢業(yè)了,終于長(zhǎng)大了一些,可以自己做主了。 孩子,我也很高興你終于長(zhǎng)大了些,不...
    在路上的南方姑娘閱讀 173評(píng)論 0 1

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