DataBinding最全使用說明

DataBinding最全使用說明

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

DataBinding會自動在build目錄下生成類. 因為被集成進(jìn)AndroidStudio所以不需要你手動編譯會實時編譯, 并且支持大部分代碼補(bǔ)全.

啟用DataBinding

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

因為怕你們沒注意到我寫在文章開頭

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

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

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

一行代碼實現(xià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é)點必須是<layout> . 同時layout只能包含一個View標(biāo)簽. 不能直接包含<merge>

data

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

variable

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

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

通過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" />
  <!--因為User已經(jīng)導(dǎo)入, 所以可以簡寫類名-->
    <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, 可以隨意自定義, 然后就會使用type中的類-->
復(fù)制代碼

Tip: user代表UserBean這個類, 可以使用UserBean中的方法以及成員變量. 如果是getxx()會自動識別為xx. 注意不能使用字符串android, 否則會報錯無法綁定.

class

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

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

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

Tip:注意沒有代碼自動補(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這個類根據(jù)布局文件名生成(id+Binding)
    ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

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

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

alias

<variable>標(biāo)簽如果需要導(dǎo)入(import)兩個同名的類時可以使用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其他布局的時候可能需要傳遞變量(variable)值過去

<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ù)制代碼

兩個布局通過includebind:<變量名>值來傳遞. 而且兩者必須有同一個變量

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

自動布局屬性

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

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

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

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

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

數(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;
        }
    
      // 注解才會自動在build目錄BR類中生成entry, 要求方法名必須以get開頭
        @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); // 需要手動刷新
        }
    }
    復(fù)制代碼
    

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

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

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

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

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

BaseObservable和Observable的區(qū)別:

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

ObservableField

databinding默認(rèn)實現(xiàn)了一系列實現(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ù)制代碼

對于集合數(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. 上面說的這兩種只會視圖跟隨數(shù)據(jù)更新, 數(shù)據(jù)并不會跟隨視圖刷新.
  3. ObservableField同樣支持addOnPropertyChangedCallback監(jiān)聽屬性改變

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

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

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

這種雙向綁定存在一個很大的問題就是會死循環(huán). 數(shù)據(jù)變化(回調(diào)監(jiān)聽器)觸發(fā)視圖變化, 然后視圖又會觸發(fā)數(shù)據(jù)變化(再次回調(diào)監(jiān)聽器), 然后一直循環(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ù)沒有變化不進(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ù)我上面說的, 監(jiān)聽器至少回調(diào)兩次(數(shù)據(jù)->視圖, 視圖-> 數(shù)據(jù))

  2. 以下這種是無效的, 因為String參數(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é)就是如果沒有默認(rèn)實行的控件屬性使用雙向數(shù)據(jù)綁定 就需要你自己實現(xiàn)BindingAdapter注解

注解

@Bindable

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

@BindingAdapter

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

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

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

@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ù)第一個要求必須是View
  3. 方法名不作要求
  4. 最后這個boolean類型是可選參數(shù). 可以要求是否所有參數(shù)都需要填寫. 默認(rèn)true.
  5. 如果requireAll為false, 你沒有填寫的屬性值將為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)你定義了命名空間就必須完全遵守

例如:

// 這里省略了一個注解參數(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ù)初始化是在異步的. 會回調(diào)方法但是數(shù)據(jù)為null(成員默認(rèn)值). 所以我們必須要首先進(jìn)行判空處理.

@BindingMethods

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

該注解屬于一個容器. 內(nèi)部參數(shù)是一個@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

該注解必須有三個屬性

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

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

如果屬性名和@BindingAdapter沖突會報錯

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

@BindingConversion

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

  1. 只能修飾public static方法.
  2. 任意位置任意方法名都不限制
  3. DataBinding自動匹配被該注解修飾的方法和匹配參數(shù)類型
  4. 返回值類型必須和屬性setter方法匹配, 且參數(shù)只能有一個
  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è)置一個字符串. 這樣就不會匹配Background的int參數(shù)類型. 然后DataBinding就會檢索匹配該類型的@BindingConversion方法. 然后轉(zhuǎn)換.

注意android:text如果想用int自動轉(zhuǎn)String是不可以的, 因為int值會被識別為resource id. @BindingConversion無法工作.

@InverseMethod

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

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

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

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

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

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

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

簡單示例:

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

  @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:

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

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

@InverseBindingAdapter

參數(shù):

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

介紹

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

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

在你綁定DataBinding時候回自動調(diào)用這個數(shù)據(jù)變更方法, (這個數(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ù)變更方法(官方源碼簡化版):

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

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

    // 如果全部為null不要監(jiān)聽器
    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)有一個監(jiān)聽器就先刪除
    final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
      view.removeTextChangedListener(oldValue);
    }

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

這里用到一個InverseBindingListener

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

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

@InverseBindingMethods

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

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

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

必須與@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)值通過屬性指定變化監(jiān)聽和返回方法

在自動生成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))); // 存儲到數(shù)據(jù)
            }
        }
    };
復(fù)制代碼

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

// 該方法會在綁定布局的時候回調(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

          // 重點是這段代碼, 將上面創(chuàng)建的監(jiān)聽器傳入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這兩個注解是DataBinding自動生成Java類時使用的.

  • Bindable

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

  • BindingAdapter

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

  • BindingMethod/BindingMethods

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

  • BindingConversion

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

  • InverseMethod

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

  • InverseBindingAdapter

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

  • InverseBindingMethod/InverseBindingMethods

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

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

DataBindingAdapter

表達(dá)式

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

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

避免空指針

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

這是因為官方已經(jīng)用DataBinding的@BindingAdapter注解重寫了很多屬性. 并且里面進(jìn)行了判空處理.

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

.....

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

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

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

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

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

就等價于

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ù)制代碼

上面這種寫法會報錯

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

因為<符號需要轉(zhuǎn)義.

常用轉(zhuǎn)義字符

? 空格 &nbsp避消; &#160;

< 小于號 &lt型凳; &#60;

大于號 &gt; &#62嗦董;

& 與號 &amp; &#38瘦黑; " 引號 &quot展懈; &#34; ‘ 撇號 &apos供璧; &#39存崖; × 乘號 &times; &#215睡毒; ÷ 除號 &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ù)組都可以用[]來得到元素

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還沒有復(fù)制就會使用默認(rèn)值顯示.

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

上下文

DataBinding本身提供了一個名為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="活動"
              />

/...
<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是可以直接忽略該符號. 例如tv_name直接寫tvName.

謝謝 lambda 指出錯誤

不論順序都可以引用

使用Class

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

事件綁定

事件綁定分為兩種:

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

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

方法引用

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

直接通過View的屬性來調(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()就會報錯. 因為對于默認(rèn)事件需要統(tǒng)一參數(shù). 必須加上activity.click(View v)

監(jiān)聽綁定

上面提到的都不能向回調(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("吳彥祖"); // 順序無所謂
復(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會將控件所有的setter方法全部暴露為xml屬性. 是不是很方便??

DataBinding組件

ViewDataBinding

自動生成的DataBinding類都繼承自該類. 所以都擁有該類的方法

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

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

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

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

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

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

這里有三個方法需要重點講解:

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

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

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

OnRebindCallback:

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

    mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /**
       * 綁定之前
       * @param binding
       * @return 如果返回true就會綁定布局, 返回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也有個數(shù)據(jù)變更監(jiān)聽器, 可以監(jiān)聽Variable的設(shè)置事件

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

  /**
       * 會在DataBinding設(shè)置數(shù)據(jù)的時候回調(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ù)制代碼

還有兩個不常用的方法, 檢索視圖是否被綁定, 如果沒有綁定返回nul

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

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

其他的方法

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

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

DataBindingComponent

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

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

  1. 創(chuàng)建一個MyDataBindingComponent實現(xiàn)接口DataBindingComponent
  2. 創(chuàng)建MyBindingAdapter類, 用@BindingAdapter修飾其成員方法(不需要靜態(tài))
  3. 在MyDataBindingComponent寫入一個get**方法()來返回該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è)置組件需要每次綁定視圖都設(shè)置, 否則就會報錯.

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

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

注意

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

推薦插件

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

DataBindingModelFormatter

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

DataBindingSupport

自動生成DataBinding所需的XML格式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末演顾,一起剝皮案震驚了整個濱河市供搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠至,老刑警劉巖葛虐,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棉钧,居然都是意外死亡屿脐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門宪卿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來的诵,“玉大人,你說我怎么就攤上這事佑钾∥靼蹋” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵休溶,是天一觀的道長代赁。 經(jīng)常有香客問我,道長兽掰,這世上最難降的妖魔是什么芭碍? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮禾进,結(jié)果婚禮上豁跑,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好艇拍,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布狐蜕。 她就那樣靜靜地躺著,像睡著了一般卸夕。 火紅的嫁衣襯著肌膚如雪层释。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天快集,我揣著相機(jī)與錄音贡羔,去河邊找鬼。 笑死个初,一個胖子當(dāng)著我的面吹牛乖寒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播院溺,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼楣嘁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了珍逸?” 一聲冷哼從身側(cè)響起逐虚,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谆膳,沒想到半個月后叭爱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漱病,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年买雾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缨称。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凝果,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睦尽,到底是詐尸還是另有隱情,我是刑警寧澤型雳,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布当凡,位于F島的核電站,受9級特大地震影響纠俭,放射性物質(zhì)發(fā)生泄漏沿量。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一冤荆、第九天 我趴在偏房一處隱蔽的房頂上張望朴则。 院中可真熱鬧,春花似錦钓简、人聲如沸乌妒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撤蚊。三九已至古掏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侦啸,已是汗流浹背槽唾。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留光涂,地道東北人庞萍。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像忘闻,于是被迫代替她去往敵國和親钝计。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容