DataBinding最全使用說明

若是你要在Android實現MVVM架構, 那麼DataBinding是你的不二選擇. MVVM也是目前全部前端/iOS/Android領域主流發展方向前端

  1. 更少的代碼
  2. 更強大的容錯性
  3. 更快的迭代速度
  4. 更高的可讀性

本文與2019基於Kotlin再編輯java

前言android

  1. 不要企圖使用LiveData取代DataBinding, DataBinding自己就兼容LiveData屬性
  2. 不管項目大小MVVM都優於MVP
  3. 這是主流也是將來

啓用 DataBinding會自動在build目錄下生成類. 由於被集成進AndroidStudio因此不須要你手動編譯會實時編譯, 而且支持大部分代碼補全.git

apply plugin: "kotlin-kapt" // Kotlin 使用 Databinding必須添加

android{
  /.../
      dataBinding {
        enabled = true;
    }
}
複製代碼

開頭github

  • Databinding不是替代ButterKnife之類的 findById只是他的一個小小的輔助功能而已, 我推薦使用Kotlin來解決這個需求;
  • Databinding的大部分狀況下錯誤提示很完善, 個別XML書寫錯誤也易於排查
  • 我想強調的是Xml中的@{} 只作賦值或者簡單的三元運算或者判空等不要作複雜運算, 不然違背解耦原則.
  • 業務邏輯應該儘可能在Model中
  • ViewModel屬於DataBinding自動生成的類

MVP對比MVVM的劣勢api

  1. MVP經過接口回調實現致使代碼可讀性差, 閱讀順序不連貫
  2. MVP沒法實現雙向數據綁定
  3. MVP的實現因人而異, 差別性致使閱讀性差
  4. MVP的代碼量比MVC還要多, 屬於經過提高代碼量來解耦, 代碼量比MVVM幾何倍增
  5. 前端任何平臺都開始趨向於MVVM, Web領域MVVM屬於最成熟的應用

我開源一個基於Kotlin和Databinding特性的RecyclerView庫: BRV, 具有無與倫比的簡潔和MVVM特性;數組

我平時項目開發必備框架安全

  1. Android上最強網絡請求 Net
  2. Android上最強列表(包含StateLayout) BRV
  3. Android最強缺省頁 StateLayout
  4. JSON和長文本日誌打印工具 LogCat
  5. 支持異步和全局自定義的吐司工具 Tooltip
  6. 開發調試窗口工具 DebugKit
  7. 一行代碼建立透明狀態欄 StatusBar

佈局

佈局文件markdown

<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>
複製代碼

layout網絡

佈局根節點必須是<layout> . 同時layout只能包含一個View標籤. 不能直接包含<merge>

data

<data>標籤的內容即DataBinding的數據. data標籤只能存在一個.

variable

經過<variable>標籤能夠指定類, 而後在控件的屬性值中就可使用

<data>
	<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
複製代碼

經過DataBinding的setxx()方法能夠給Variable設置數據. name值不能包含_下劃線

import

第二種寫法(導入), 默認導入了java/lang包下的類(String/Integer). 能夠直接使用被導入的類的靜態方法.

<data>
  <!--導入類-->
    <import type="com.liangfeizc.databindingsamples.basic.User" />
  <!--由於User已經導入, 因此能夠簡寫類名-->
    <variable name="user" type="User" />
</data>
複製代碼

使用類

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}" />
<!--user就是在Variable標籤中的name, 能夠隨意自定義, 而後就會使用type中的類-->
複製代碼

Tip: user表明UserBean這個類, 可使用UserBean中的方法以及成員變量. 若是是getxx()會自動識別爲xx. 注意不能使用字符串android, 不然會報錯沒法綁定.

class

<data>標籤有個屬性<class>能夠自定義DataBinding生成的類名以及路徑

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

<!--自定義生成路徑以及類型-->
<data class=".CustomDataBinding"></data> <!--自動在包名下生成包以及類-->
複製代碼

Tip:注意沒有代碼自動補全. 自定義路徑Module/build/generated/source/apt/debug/databinding/目錄下, 基本上不須要自定義路徑

默認:

public class MainActivity extends AppCompatActivity {

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

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

    UserBean userBean = new UserBean();
    userBean.setUserName("drake");

    // setUser這個方法根據Variable標籤的name屬性自動生成
    viewDataBinding.setUser(userBean);
  }
}
複製代碼

alias

<variable>標籤若是須要導入(import)兩個同名的類時可使用alias屬性(別名屬性)

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
複製代碼

include

在include其餘佈局的時候可能須要傳遞變量(variable)值過去

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

....

<include layout="@layout/include_demo" bind:userName="@{userName}"/>
複製代碼

include_demo

<data>

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

...

android:text="@{userName}"
複製代碼

兩個佈局經過includebind:<變量名>值來傳遞. 並且二者必須有同一個變量

DataBinding不支持merge標籤傳遞變量

自動佈局屬性

DataBinding對於自定義屬性支持很是好, 只要View中包含setter方法就能夠直接在佈局中使用該屬性(這是由於DataBinding的庫中官方已經幫你寫好了不少自定義屬性)

public void setCustomName(@NonNull final String customName) {
    mLastName.setText("吳彥祖");
  }
複製代碼

而後直接使用(可是IDE沒有代碼補全)

app:customName="@{@string/wuyanzu}"
複製代碼

可是setter方法只支持單個參數. app:這個命名空間能夠隨意

數據雙向綁定

數據刷新視圖

BaseObservable

若是須要數據變化是視圖也跟着變化則須要使用到如下兩種方法

有兩種方式:

繼承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); // 須要手動刷新
    }
}
複製代碼
  • 簡化用法只須要數據模型繼承BaseObservable便可, 而後每次變動數據後調用notify()函數既能夠刷新視圖. 不須要註解.

    observableUser.name
    observableUser.notifyChange()
    複製代碼
  • 若是你沒法繼承能夠經過實現接口方式也能夠. 查看BaseObservable實現的接口本身實現便可, 也能夠複製代碼示例.

還能夠監聽屬性改變事件

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

  }
});
複製代碼

屬性第一次改變時會回調兩次, 以後都只回調一次. 若是使用notifyChange()不會獲得id(即i等於0). 使用

notifyPropertyChanged(i)就能夠在回調裏面獲得id.

BaseObservable和Observable的區別

  1. BaseObservable是實現了Observable的類, 幫咱們實現了監聽器的線程安全問題.
  2. BaseObservable使用了PropertyChangeRegistry來執行OnPropertyChangedCallback
  3. 因此我不推薦你直接實現Observable.

ObservableField

這屬於第二種方式, databinding默認實現了一系列實現Observable接口的字段類型

BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
複製代碼

示例

public class PlainUser {
  public final ObservableField<String> firstName = new ObservableField<>();
  public final ObservableField<String> lastName = new ObservableField<>();
  public final ObservableInt age = new ObservableInt();
}
複製代碼

對於集合數據類型ObservableArrayMap/ObservableArrayLis/ObjservableMap等集合數據類型

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
複製代碼

使用

<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"/>
複製代碼

Tip:

  1. 還支持ObservableParcelable<Object>序列化數據類型
  2. 上面說的這兩種只會視圖跟隨數據更新, 數據並不會跟隨視圖刷新.
  3. ObservableField一樣支持addOnPropertyChangedCallback監聽屬性改變

若是數據爲LiveData一樣支持, 而且ViewDataBinding能夠設置生命週期.

視圖刷新數據

經過表達式使用@=表達式就能夠視圖刷新的時候自動更新數據, 可是要求數據實現如下兩種方式修改纔會觸發刷新

<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textNoSuggestions" android:text="@={model.name}"/>
複製代碼

這種雙向綁定存在一個很大的問題就是會死循環. 數據變化(回調監聽器)觸發視圖變化, 而後視圖又會觸發數據變化(再次回調監聽器), 而後一直循環, 設置相同的數據也視爲數據變化.

因此咱們須要判斷當前變化的數據是否等同於舊數據

public class CustomBindingAdapter {

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

    if (!haveContentsChanged(text, oldText)) {
      return; // 數據沒有變化不進行刷新視圖
    }
    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;
  }
}
複製代碼

Tip:

  1. 根據我上面說的, 監聽器至少回調兩次(數據->視圖, 視圖-> 數據)

  2. 如下這種是無效的, 由於String參數傳遞屬於引用類型變量並非常量, 須要用equals()

    // 本段截取官方源碼, 我也不知道這sb爲何這麼寫
    if (text == oldText || (text == null && oldText.length() == 0)) {
      return; 
    }
    
    /**/ 複製代碼

    正確

    if (text == null || text.equals(oldText) || oldText.length() == 0) {
      return;
    }
    複製代碼

總結就是若是沒有默認實行的控件屬性使用雙向數據綁定 就須要你本身實現BindingAdapter註解

註解

DataBinding經過註解來控制ViewModel的類生成

@Bindable

用於數據更新自動刷新視圖. 後面的數據綁定提到.

@BindingAdapter

建立一個XML屬性和函數, 而後在屬性中進行設置數據操做會進入該函數.

圖片加載框架能夠方便使用此方法.

@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
  public static void loadImage(ImageView view, String url, Drawable error) {
    Glide.with(view.getContext()).load(url).into(view);
  }
複製代碼
  1. 修飾方法, 要求方法必須public static
  2. 第一個參數必須是控件或其父類
  3. 方法名隨意
  4. 最後這個boolean類型是可選參數. 能夠要求是否全部參數都須要填寫. 默認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}" />
複製代碼

能夠看到命名空間能夠隨意, 可是若是在BindingAdapter的數組內你定義了命名空間就必須徹底遵照

例如:

// 這裏省略了一個註解參數. 
@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);
  }
複製代碼

Tip: 若是你的數據初始化是在異步的. 會回調方法可是數據爲null(成員默認值). 因此咱們必需要首先進行判空處理.

Kotlin實現有兩種方法

單例類+@JvmStatic註解

object ProgressAdapter {

    @JvmStatic
    @BindingAdapter("android:bindName")
    fun setBindName(view: View, name:String){

    }
}
複製代碼

頂級函數

@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){

}

// 因爲頂級函數太多影響代碼補全建議使用頂級擴展函數, 以後也能夠在代碼中方便使用

@BindingAdapter("android:bindName")
fun View.setBindName( name:String){
   
}
複製代碼

@BindingMethods

若是你想建立一個XML屬性而且和View中的函數關聯(即會自動使用屬性值做爲參數調用該函數). 就應該使用@BindingMethods註解一個類(該類無限制甚至能夠是一個接口).

若是說@BindingAdapter是建立一個新的函數功能給控件使用, 那麼BindingMethod就是引導DataBinding使用控件自身的函數.

該註解屬於一個容器. 內部參數是一個@BindingMethod數組, 只能用於修飾類;

任意類或接口, 不須要覆寫任何函數

官方示例:

@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 {
}
複製代碼

@BindingMethod

註解參數(必選)

  1. type: 字節碼 即你的控件類
  2. attribute: XML屬性
  3. method: 函數名 即控件中的函數名稱

注意

  • 若是屬性名和@BindingAdapter定義的XML屬性相同會衝突報錯
  • 若是控件類中已經存在一個和你定義的屬性相關聯的函數(例setName函數和android:name屬性就相關聯)則會優先執行該函數

@BindingConversion

屬性值自動進行類型轉換

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

官方示例:

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);
    }
}
複製代碼

我寫的Kotlin示例

@BindingConversion
fun int2string(integer:Int):String{
    Log.d("日誌", "(CusView.kt:92) int2string ___ integer = [$integer]")

    return integer.toString()
}
複製代碼

XML

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable name="m" type="com.example.architecture.Model" />

    </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">


       <com.example.architecture.CusView android:bindName="@={m.age}" android:layout_width="wrap_content" android:layout_height="wrap_content" />


    </FrameLayout>

</layout>
複製代碼

我這代碼實際上會報錯, 由於涉及到雙向數據綁定, @BindingConversion只會在數據設置視圖的時候生效. 可是若是是視圖設置數據則會走其餘函數(get), 若是該函數返回的類型和Model中的類型不匹配則會報異常, 除非你將那個函數改成類型匹配的.

或者去掉=符號不使用雙向數據綁定

android:text不能使用int轉爲string, 由於他自己能正常接收int(做爲resourceID). 而後會報

android.content.res.Resources$NotFoundException: String resource ID #0xa
複製代碼

@InverseMethod

該註解屬於AndroidStudio3以後提供的inverse系列的新註解, 所有都是針對數據雙向綁定.

在數據和視圖的數據不統一時可使用該註解@InverseMethod解決數據轉換的問題

例如數據模型存儲用戶的id可是視圖不顯示id而是顯示用戶名(數據和視圖的類型不一致), 咱們就須要在二者之間轉換.

咱們須要兩個函數: 設置數據到視圖的函數 稱爲set / 設置視圖變動到數據的函數 稱爲get

  • set和get都至少要有一個參數
  • 自身參數必須和另外一個函數的返回值對應(否則怎麼叫轉換)

簡單示例:

在用戶id和用戶名之間轉換. 存儲id可是顯示的時候顯示用戶名

class Model {
  
  var name = "設計師"
  
   @InverseMethod("ui2data")
    fun data2ui():String{

        return "設計師金城武"
    }

    fun ui2data():String{
        return "設計師吳彥祖"
    }
}
複製代碼

使用

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable name="m" type="com.example.architecture.Model" />

    </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">


       <com.example.architecture.CusView android:text="@{m.data2ui(m.name)}" android:layout_width="wrap_content" android:layout_height="wrap_content" />

    </FrameLayout>

</layout>
複製代碼

@InverseBindingAdapter

參數:

  • String attribute 屬性值(必填)
  • String event 非必填, 默認值等於 <attribute>AttrChanged

他和@BindingAdapter配合實現雙向數據綁定

徹底的雙向數據綁定須要三個函數

  1. set (數據到視圖)
  2. get (視圖到數據)
  3. notify (通知Databinding視圖已經刷新能夠更新數據(Model)了)

set函數, 以前已經寫過了

@BindingAdapter("android:bindName")
fun TextView.setBindName(name:String?){
    if (name.isNullOrEmpty() && name != text) {
        text = name
    }
}
複製代碼

get函數

@InverseBindingAdapter(attribute = "android:bindName", event = "cus_event")
fun TextView.getBindName():String{

	// 這裏你能夠對視圖上的數據進行處理最終設置給Model層

    return text.toString()
}
複製代碼
  • 不容許存在更多參數
  • 返回值類型必須是綁定的數據類型

notify函數 視圖變化後要通知Databinding開始設置Model層, 一樣要用到@BindingAdapter, 不一樣的是參數要求只能爲InverseBindingListener.

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

  // 這個函數是監聽TextWatch 官方源碼 固然不一樣的需求不一樣的監聽器
   doAfterTextChanged {
       inverseBindingListener.onChange() // 這行代碼執行即通知數據刷新
   }

}
複製代碼

InverseBindingListener 是個接口只有一個函數, 他是notify函數必要的參數.

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

@InverseBindingMethods

@BindingMethods類似

可是@InverseBindingMethods是視圖變動數據(get函數), 而BindingMethods是數據到視圖(set函數)

參數

public @interface InverseBindingMethod {

    /** * 控件的類字節碼 */
    Class type();

    /** * 自定義的屬性 */
    String attribute();

    /** * nitify函數的名稱 即用於通知數據更新的函數 */
    String event() default "";

    /** * 控件自身的函數名稱, 若是省略即自動生成爲 {attribute}AttrChange */
    String method() default "";
}
複製代碼

若是說BindingMethods是關聯setter方法和自定義屬性, 那麼InverseBindingMethods就是關聯getter方法和自定義屬性;

setter是更新視圖的時候使用, 而getter方法是更新數據時候使用的

@BindingMethods要多一個函數即notify函數用於通知更新

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

   doAfterTextChanged {
       inverseBindingListener.onChange()
   }

}
複製代碼

示例:

@InverseBindingMethods( InverseBindingMethod( type = CusView::class, attribute = "android:bindName", method = "getName", event = "cus_event" ) )
object Adapter {

}
複製代碼
  • 若是attribute屬性值屬於不存在的屬性, 則須要再建立一個BindingAdapter自定義屬性來處理.

查看下生成類中的視圖更新數據的實現源碼

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; // 拿到數據

    dataJavaLangObjectNull = (data) != (null);
    if (dataJavaLangObjectNull) {
      data.setName(((java.lang.String) (callbackArg_0))); // 存儲到數據
    }
  }
};
複製代碼

因此若是你沒用重寫Inverse的數據變動方法將沒法讓視圖通知數據刷新.

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

          // 重點是這段代碼, 將上面建立的監聽器傳入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);
        }
    }
複製代碼

總結

@BindingBuildInfo@Untaggable這兩個註解是DataBinding自動生成Java類時使用的.

  • Bindable

    設置數據刷新視圖. 自動生成BR的ID

  • BindingAdapter

    設置自定義屬性. 能夠覆蓋系統原有屬性

  • BindingMethod/BindingMethods

    關聯自定義屬性到控件原有的setter方法

  • BindingConversion

    若是屬性不能匹配類型參數將自動根據類型參數匹配到該註解修飾的方法來轉換

  • InverseMethod

    負責實現視圖和數據之間的轉換

  • InverseBindingAdapter

    視圖通知數據刷新的

  • InverseBindingMethod/InverseBindingMethods

    視圖通知數據刷新的(若是存在已有getter方法可用的狀況下)

  • BindingMethods系優先級高於BindingAdapter系列

  • 全部註解的功能都是基於XML屬性值爲Databinding表達式才生效(即@{})

建議參考官方實現源碼:

DataBindingAdapter

表達式

這裏指的是XML文件中使用的表達式(用於賦值變量), @{}裏面除了能夠執行方法之外還能夠寫表達式, 而且支持一些特有表達式

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

避免空指針

variable的值即便設置null或者沒有設置也不會出現空指針異常.

這是由於官方已經用DataBinding的@BindingAdapter註解重寫了不少屬性. 而且裏面進行了判空處理.

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

.....

android:text="@{userName}"
複製代碼

不會出現空指針異常.

dataBinding.setUserName(null);
複製代碼

而且還支持特有的非空多元表達式

android:text="@{user.displayName ?? user.lastName}"
複製代碼

就等價於

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
複製代碼

仍是須要注意數組越界的

集合

集合不屬於java.lang*下, 須要導入全路徑.

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

<variable name="map" type="java.util.Map<String, String>"/>
複製代碼

上面這種寫法會報錯

Error:與元素類型 "variable" 相關聯的 "type" 屬性值不能包含 '<' 字符。
複製代碼

由於<符號須要轉義.

經常使用轉義字符

空格 &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;"/>
複製代碼

集合和數組均可以用[]來獲得元素

android:text="@{map["firstName"]}"
複製代碼

字符串

若是想要在@{}中使用字符串, 可使用三種方式

第一種:

android:text='@{"吳彥祖"}'
複製代碼

第二種:

android:text="@{`吳彥祖`}"
複製代碼

第三種:

android:text="@{@string/user_name}"
複製代碼

一樣支持@color或@drawable

格式化字符串

首先在strings中定義<string>

<string name="string_format">名字: %s  性別: %s</string>
複製代碼

而後就可使用DataBinding表達式

android:text="@{@string/string_format(`吳彥祖`, `男`)}"
複製代碼

輸出內容:

名字: 吳彥祖 性別: 男
複製代碼

默認值

若是Variable尚未複製就會使用默認值顯示.

android:text="@{user.integral, default=`30`}"
複製代碼

上下文

DataBinding自己提供了一個名爲context的Variable. 能夠直接使用. 等同於View的getContext().

android:text="@{context.getApplicationInfo().toString()}"
複製代碼

引用其餘控件

<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}" />
複製代碼

引用包含_的控件id是能夠直接忽略該符號. 例如tv_name直接寫tvName.

謝謝 lambda 指出錯誤

不論順序均可以引用

使用Class

若是想用Class做爲參數傳遞, 那麼該Class不能直接經過靜態導入來使用. 須要做爲字段常量來使用

函數回調

DataBinding還支持在XML中綁定函數參數類型, 而且仍是Lambda和高階函數類型, 這點比Java還先進.

對象

即直接將對象做爲和屬性等同的方式在XML使用. 這就必須先手動建立一個對象. 稍顯麻煩.

高階函數

建立自定義屬性

object EventDataBindingComponent {

    /** * 在綁定視圖時能夠用於Model來處理UI, 因爲破壞視圖和邏輯解耦的規則不是很建議使用 * 這會致使不方便業務邏輯進行單元測試 * * @see OnBindViewListener 該接口支持泛型定義具體視圖 * * @receiver View * @param block OnBindViewListener<View> */
    @JvmStatic
    @BindingAdapter("view")
    fun View.setView(listener: OnBindViewListener) {
        listener.onBind(this)
    }
}
複製代碼

上面使用到的接口

interface OnBindViewListener {
    fun onBind(v: View) } 複製代碼

高階函數

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="v" 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="設計師吳彥祖" android:onClick="@{v::click}"/>

    </LinearLayout>
</layout>
複製代碼

在XML中使用高階函數須要匹配以下規則

  1. BindingAdapter的函數參數要求是一個接口, 不支持Kotlin的函數類型參數
  2. 接口只容許一個函數
  3. 接口的方法簽名(返回值|參數)和傳遞的高階函數匹配

Lambda

高階函數不容許自定義傳遞參數(不然須要修改接口). 因此可使用Lambda來進行控制.

建立一個多參數的函數

fun onBinding(v:View, name:String){
  Log.d("日誌", "(MainActivity.kt:45) this = [$v] name = [$name]")
}
複製代碼

XML使用

view="@{(view) -> v.onBinding(view, `吳彥祖`)}"
複製代碼

若是不使用參數

view="@{() -> v.onBinding(`吳彥祖`)} 複製代碼

ViewDataBinding

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

void addOnRebindCallback(OnRebindCallback listener) // 添加綁定監聽器, 能夠在Variable被設置的時候回調 void removeOnRebindCallback(OnRebindCallback listener) // 刪除綁定監聽器 View getRoot() // 返回被綁定的視圖對象 abstract void invalidateAll() // 使全部的表達式無效而且馬上從新設置表達式. 會從新觸發OnRebindCallback回調(能夠看作重置) abstract boolean setVariable(int variableId, Object value) // 能夠根據字段id來設置變量 void unbind() // 解綁綁定, ui不會根據數據來變化, 可是監聽器仍是會觸發的 複製代碼

這裏有三個方法須要重點講解:

abstract boolean hasPendingBindings() // 當ui須要根據當前數據變化時就會返回true(數據變化後有一瞬間) void executePendingBindings() // 強制ui馬上刷新數據,  複製代碼

當你改變了數據之後(在你設置了Observable觀察器的狀況下)會立刻刷新ui, 可是會在下一幀纔會刷新UI, 存在必定的延遲時間. 在這段時間內hasPendingBindings()會返回true. 若是想要同步(或者說馬上)刷新UI能夠立刻調用executePendingBindings().

OnRebindCallback

該監聽器能夠監聽到佈局綁定的生命週期

mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /** * 綁定以前 * @param binding * @return 若是返回true就會綁定佈局, 返回false則取消綁定 */
      @Override public boolean onPreBind(ViewDataBinding binding) {
        return false;
      }

      /** * 若是取消綁定則回調該方法(取決於onPreBind的返回值) * @param binding */
      @Override public void onCanceled(ViewDataBinding binding) {
        super.onCanceled(binding);
      }

      /** * 綁定完成 * @param binding */
      @Override public void onBound(ViewDataBinding binding) {
        super.onBound(binding);
      }
    });
複製代碼

OnPropertyChangedCallback

DataBinding也有個數據變動監聽器, 能夠監聽Variable的設置事件

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

  /** * 會在DataBinding設置數據的時候回調 * @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;
    }
  }
});
複製代碼

DataBindingUtil

DataBinding不只能夠綁定Activity還能夠綁定視圖內容(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) 複製代碼

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

static <T extends ViewDataBinding> T getBinding(View view) // 和getBinding不一樣的是若是視圖沒有綁定會去檢查父容器是否被綁定 static <T extends ViewDataBinding> T findBinding(View view) 複製代碼

其餘的方法

// 根據傳的BR的id來返回字符串類型. 可能用於日誌輸出
static String convertBrIdToString(int id) 複製代碼

例如BR.name這個字段對應的是4, 就可使用該方法將4轉成"name"

DataBindingComponent

默認狀況下BindingAdapter註解針對全部的XML屬性均可以使用. 而經過制定不一樣的DatabindingComponent能夠切換這些自定義屬性.

建立DatabindingComponent的步驟:

  1. 建立自定義類, 類中存在包含使用@BindingAdapter的函數, 無需靜態函數.

    這個時候AndroidStudio會自動生成DatabindingComponnent接口

  2. 建立DatabindingComponent派生類, 這個時候會提示有方法要求覆寫. 若是你省略第一步驟則不會有.

  3. 經過DataBindingUtils工具將你自定義的派生類設置到Databinding中, 這裏包含全局默認和單例.

第一步

class PinkComponent {

    @BindingAdapter("android:bindName")
    fun TextView.setBindName(name:String?){

        if (!name.isNullOrEmpty() && name != text) {
            text = "數據體"
        }
    }

    @BindingAdapter("android:bindNameAttrChanged")
    fun TextView.notifyBindName(inverseBindingListener: InverseBindingListener){

        doAfterTextChanged {
            inverseBindingListener.onChange()
        }

    }

    @InverseBindingAdapter(attribute = "android:bindName")
    fun TextView.getBindName():String{

        return text.toString()
    }
}
複製代碼

第二步

class CusComponent : DataBindingComponent {

    override fun getPinkComponent(): PinkComponent {
        return PinkComponent() // 此處不能返回null
    }
}
複製代碼

第三步

設置默認組件都是由DataBindingUtils設置, 可是方法也有所不一樣

static void setDefaultComponent(DataBindingComponent bindingComponent) static DataBindingComponent getDefaultComponent() 複製代碼

以上這種設置必須在綁定視圖以前設置, 而且是默認全局的, 只須要設置一次.

static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent) 複製代碼

若是你沒有執行setDefaultComponent則選擇經過函數單獨傳入, 則每次都要傳入不然報錯.

DatabindingComponent只能使用@BindingAdapter註解

注意

  1. 可使用include不過不能做爲root佈局. merge不能使用
  2. 若是沒有自動生成DataBinding類能夠先寫個variable(或者make module下)
  3. 即便你沒有綁定數據(你可能會在網絡請求成功裏面綁定數據), 可是隻要視圖建立完成就會自定綁定數據. 這個時候數據是空對象. 空對象的字段也會有默認值(String的默認值是NULL, TextView就會顯示NULL); 而且若是你用了三元表達式, 空對象的三元表達式都爲false; 因此建議不要考慮空對象的狀況;
  4. 若是你給一個要求值是布爾類型值的自定義屬性(BindingAdapter)賦值一個函數, 空指針的狀況會返回false;

推薦插件

DataBindingSupport

經過快捷鍵(alt + enter)在XML佈局中自動建立表達式和節點 , AS4失效

DataBindingConvert

使用快捷鍵快速將包裹布局爲layout, AS4可用

相關文章
相關標籤/搜索