Android Jetpack架構組件(八)之DataBinding

1、DataBinding簡介

在傳統的Android應用開發中,佈局文件一般只負責應用界面的佈局工做,若是須要實現頁面交互就須要調用setContentView()將Activity、fragment和XML佈局文件關聯起來。而後經過控件的id找到控件,接着在頁面中經過代碼對控件進行邏輯處理。在這種傳統的開發方式中,頁面承擔了大部分的工做量,大量的邏輯處理須要在Activity、Fragment中進行處理,所以頁面顯得臃腫不堪,維護起來也很困難,爲了減輕頁面的工做量,Google提出了DataBiiding(視圖綁定)。java

DataBinding的出現讓佈局文件承擔了本來屬於Activity、Fragment頁面的部分邏輯,使得Activity頁面和XML佈局之間的耦合度進一步下降。DataBinding主要有如下特色:android

  • 代碼更加簡介,可讀性高,可以在XML文件中實現UI控制。
  • 再也不須要findViewById操做,
  • 可以與Model實體類直接綁定,易於維護和擴展。

事實上,DataBinding和MVVM架構是分不開的,DataBinding正是Google爲了可以更好的實現MVVM架構而實現的。api

2、DataBinding基本使用

2.1 開啓viewBinding

視圖綁定功能可按模塊啓用,要在某個模塊中啓用視圖綁定,請將 viewBinding 元素添加到build.gradle 文件中,以下所示。網絡

android {
        ...
        viewBinding {
            enabled = true
        }
    }

DataBinding 是一個 support 包,添加完後上面的腳本後會發現工程的的External Libraries中多了四個aar包。分別是adapters、commen、runtime和viewbinding。架構

使用DataBinding時,若是但願在生成綁定類時忽略某個佈局文件,能夠將 tools:viewBindingIgnore="true" 屬性添加到相應佈局文件的根視圖中,以下所示。app

<LinearLayout
   ...
    tools:viewBindingIgnore="true" >
   ...
</LinearLayout>

2.2 修改佈局文件

使用DataBinding的第一步,就是先改造XML文件。其實改造佈局文件也特別簡單,只須要在原來文件內容的基礎上,最外層改成<layout>標籤便可,以下所示。ide

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
       ... 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" 
            android:textSize="24dp"
            android:text="HelloWord" />

       ... 
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

固然,咱們也能夠選中根佈局,按住【Alt + 回車鍵】而後選擇 【Convert to data binding layout】也能夠生成 DataBinding 須要的佈局規則。在佈局最外層加layout標籤後,從新編譯項目,DataBinding庫就會生成對應的Binding類,該類用來實現XML佈局文件與Model類的綁定,代碼以下。工具

public class ActivityMainBindingImpl extends ActivityMainBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    @NonNull
    private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x1L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
            return variableSet;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        // batch finished
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): null
    flag mapping end*/
    //end
}

生成的ActivityMainBindingImpl代碼位於app/build目錄下。生成Binding類的名字很特殊,它與XML佈局文件的名字有對應關係,具體的聯繫就是,以XML佈局文件爲準,去掉下劃線,全部單次以大駝峯的形式按順序拼接,最後再加上Binding。好比,個人XML佈局文件名是activity_main.xml,生成的Binding類名就是ActivityMainBinding。佈局

2.3 綁定佈局

沒有使用DataBinding的時候,爲了將XML佈局文件與Activity進行綁定,須要調用Activity的setContentView()方法,或者是在Fragment中調用LayoutInflate的inflate()方法來進行佈局的綁定。若是使用了DataBinding以後,就須要使用DataBindingUtil類來進行視圖的綁定,以下所示。測試

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding; 
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding= DataBindingUtil.setContentView(this,R.layout.activity_main);     
    }
}

使用DataBindingUtil類的setContentView()方法對Activity進行綁定,其返回值就是工具生成的Binding類。若是是Fragment,則對應的綁定佈局的代碼以下。

private ActivityMainBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ActivityMainBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

2.4 添加data標籤

通過前面的步驟後,咱們已經使用DataBinding將XML文件與UI組件綁定起來,若是要在XML文件中接受Model數據,就須要用到data標籤與variable標籤。

在XML文件的layout標籤下,建立data標籤,在data標籤中再建立variable標籤,variable標籤主要用到的就是name屬性和type屬性,相似於Java語言聲明變量時,須要爲該變量指定類型和名稱。新建一個名爲UserModel的實體類,代碼以下。

public class UserModel {
    private String firstName;
    private String lastName;

    public UserModel(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }
}

而後在佈局的 data 標籤裏聲明要使用到的變量名、類的全路徑等信息,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
      <variable
          name="user"
          type="com.xzh.jetpack.UserModel" />
    </data>

    ... //省略其餘代碼
</layout>

若是 User 有多處用到,也能夠直接將之 import 進來,這樣就不用每次都指明整個包名路徑了,而 java.lang.* 包中的類會被自動導入,因此能夠直接使用。

<data>
   <import type="com.xzh.jetpack.UserModel" />
   <variable
       name="user"
       type="UserModel" />
  </data>

若是存在 import 的類名相同的狀況,可使用 alias 指定別名,以下所示。

<data>
        <import type="com.xzh.jetpack.UserModel" />
        <import
            alias="TempUser"
            type="com.xzh.jetpack.uer.UserModel" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="tempUserInfo"
            type="TempUser" />
    </data>

2.5 使用variable

在XML文件中聲明好variable屬性後,接下來就能夠在XML使用它了。使用variable屬性時須要使用到佈局表達式: @{ }。能夠在佈局表達式@{ }中獲取傳入variable對象的值,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <variable
            name="user"
            type="UserModel" />
    </data>

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

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"
            android:hint="Tv1"
            android:textSize="24dp" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"
            android:textSize="24dp" />

    </LinearLayout>
</layout>

而後,咱們在UI界面中使用Binding類爲每個variable標籤使用set方法傳遞數據,以下所示。

binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
 UserModel user =new UserModel("zhang", "beijing");
 binding.setUser(user);

通過上面的處理後,咱們已經給UserModel對象設置給了Binding類,因此這裏直接運行代碼就能夠看到效果了。

2.6 響應事件

前面咱們介紹了DataBinding的一些基本用法,咱們能夠在佈局文件中對控件某些屬性進行賦值,使得Model類數據直接綁定在佈局中,並且Model屬性發生變化時,佈局文件中的內容能夠即時刷新。除了這些簡單的使用場景外,咱們還可使用DataBinding響應用戶事件。

咱們對佈局文件作一下修改,在裏面添加一個控件,而後在Activity中添加以下代碼。

binding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
     public void onClick(View v) {

       }
  });

除此以外,咱們還可使用另一種方式。佈局表達式不只能夠傳入對象的屬性,也能夠調用對象的方法。首先建立一個工具類,在類中定義響應事件的方法,以下所示。

public class ButtonClickListener {
    public void onClick(View view) {
        Log.d("ButtonClickListener","onClick...");
    }
}

而後在佈局文件中添加點擊事件的代碼,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="btnHandler"
            type="com.xzh.jetpack.databinding.ButtonClickListener" />
    </data>

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

       ...//省略其餘代碼

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按鈕"
            android:textSize="24dp"
            android:onClick="@{btnHandler.onClick}"/>

    </LinearLayout>
</layout>

在上面的代碼中,首先在data標籤中爲ButtonClickListener類聲明對象,在Button的onClick屬性中傳入佈局表達式便可。

2.7 include標籤

在Android應用開發中,爲了可以讓佈局文件獲得複用,在編寫佈局的時候咱們常常會使用include標籤,相同結構與內容的佈局文件就能夠在多處使用。可是若是一個佈局文件中使用了DataBinding,同時也使用了include標籤,那麼如何使用nclude標籤引入的佈局文件中中的數據呢。

此時,咱們須要在同一級頁面的include標籤中,經過命名控件xmlns:app來引入佈局變量User,將數據對象傳遞給二級頁面,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="user"
            type="com.xzh.jetpack.UserModel" />
    </data>

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

        <include
            layout="@layout/layout_data_binding"
            app:persondata="@{user}" />
        
        ... //省略其餘代碼
    </LinearLayout>
</layout>

佈局表達式中直接傳入頁面變量user,include標籤屬性值能夠任意取名,可是要注意的是,在二級頁面的variable標籤中的name屬性,必須與一級頁面中的include標籤屬性名一致,如layout_data_binding的代碼所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="userData"
            type="com.xzh.jetpack.UserModel" />
    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{userData.firstName}"
            android:gravity="center" />

    </LinearLayout>
</layout>

3、BindingAdapter

3.1 BindingAdapter簡介

使用DataBinding庫時,DataBinding會針對控件屬性生成對應的XXXBindingAdapter類,如TextViewBindingAdapter類,其對TextView的每一個可使用DataBinding的屬性都生成了對應的方法,並且每一個方法都使用了@BindingAdapter註解,註解中的參數就是對應View的屬性。

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings({"WeakerAccess", "unused"})
@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
        @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})

public class TextViewBindingAdapter {

    private static final String TAG = "TextViewBindingAdapters";
    @SuppressWarnings("unused")
    public static final int INTEGER = 0x01;
    public static final int SIGNED = 0x03;
    public static final int DECIMAL = 0x05;

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        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);
    }

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

    @BindingAdapter({"android:autoText"})
    public static void setAutoText(TextView view, boolean autoText) {
        KeyListener listener = view.getKeyListener();

        TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE;

        int inputType = listener != null ? listener.getInputType() : 0;
        if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
            capitalize = TextKeyListener.Capitalize.CHARACTERS;
        } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
            capitalize = TextKeyListener.Capitalize.WORDS;
        } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
            capitalize = TextKeyListener.Capitalize.SENTENCES;
        }
        view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
    }
}

BindingAdapter類中,全部的方法都是static方法,而且每一個方法都使用了@BindingAdapter註解,註解中聲明所操做的View屬性,當使用了DataBinding的佈局文件被渲染時,屬性所對應的static方法就會自動調用。

3.2 自定義BindingAdapter

除了使用庫自動生成的BindingAdapter類以外,開發者也能夠自定義BindingAdapter類,供開發者來實現系統沒有提供的屬性綁定,或者是修改原有的屬性。

例如,有下面這樣一個需求:咱們但願能夠根據圖片地址動態的改變顯示圖片。若是使用BindingAdapter 如何實現呢?

此處,咱們加載圖片使用的是glide圖片庫,而且加載圖片須要訪問網路,因此請確保申請了網路權限。

<uses-permission android:name="android.permission.INTERNET"/>

接下來,咱們編寫一個處理圖片的自定義BindingAdapter類。而後定義一個靜態方法,主要用於添加 BindingAdapter 註解,註解值是 ImageView 控件自定義的屬性名,以下所示。

public class ImageBindingAdapter {

    @BindingAdapter({"url"})
    public static void loadImage(ImageView view, String url) {
       if(!TextUtils.isEmpty(url)){
           Glide.with(view)
                   .load(url)
                   .centerCrop()
                   .placeholder(R.drawable.ic_launcher_background)//加載中顯示的圖片
                   .error(R.drawable.ic_launcher_foreground)// 錯誤後顯示的圖片
                   .into(view);
       }
    }

}

能夠發現沒,loadImage()靜態方法的兩個參數,第一個參數必須是所操做的View類型,第二個參數是圖片的地址。當 ImageView 控件的 url 屬性值發生變化時,dataBinding 就會將 ImageView 實例以及新的 url 值傳遞給 loadImage() 方法,從而實現動態改變 ImageView的相關屬性。

而後,咱們在 XML 文件中關聯變量值,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
        <ImageView
            android:layout_width="300dp"
            android:layout_height="200dp"
            app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/>
    </LinearLayout>
</layout>

須要說明的是,請注意佈局文件的最外層包含如下命名控件,這樣才能調用@BindingAdapter標籤訂義的靜態方法。

xmlns:app="http://schemas.android.com/apk/res-auto"

須要說明的是,Activity和XML綁定的代碼必定要用DataBindingUtil,以下所示。

DataBindingUtil.setContentView(this,R.layout.activity_main);

通過上面的處理後,咱們就能夠很方便的使用 imageUrl 屬性來加載網絡圖片了,而且不用擔憂線程切換問題,DataBinding 庫會自動完成線程切換。運行上面的代碼,效果以下所示。
在這裏插入圖片描述
有時候,咱們須要自定義多個屬性,那如何處理呢?和一個參數同樣,咱們只須要使用BindingAdapter添加參數便可,以下所示。

public class ImageBindingAdapter {

    @BindingAdapter(value = {"url", "placeholder", "error"})
    public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
       if(!TextUtils.isEmpty(url)){
           RequestOptions options = new RequestOptions();
           options.placeholder(placeholder);
           options.error(error);
           Glide.with(view.getContext())
                   .load(url)
                   .apply(options)
                   .into(view);
       }
    }
}

而後在佈局中傳入屬性值便可,以下所示。

<ImageView
       android:layout_width="300dp"
       android:layout_height="200dp"
       android:layout_marginTop="10dp"
       app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"
       app:placeholder="@{@drawable/icon}"
       app:error="@{@drawable/error}"/>

3.3 BindingConversion

在某些狀況下,咱們須要對設置屬性時類型進行轉化,類型轉化能夠藉助註解 @BindingConversion 來完成。例如,android:background 屬性接收的是一個 Drawable ,可是咱們在使用的時候須要給databinding 表達式中設置一個顏色值,此時就須要 @BindingConversion

首先,建立一個顏色轉化的類ColorConversion,用於將顏色值轉化爲Drawable,以下所示。

public class ColorConversion {
    
    @BindingConversion
    public static ColorDrawable colorToDrawable(int color){
        return new ColorDrawable(color);
    }
}

而後,建立一個佈局文件,添加以下代碼。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        
        <!--類型轉換-->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@{true ? @color/colorRed : @color/colorBlue}"/>
    </LinearLayout>
</layout>

在佈局中使用 @BindingConversion 註解時要使用相同類型,不然會報錯。

4、雙向綁定

DataBinding的自己是對View層狀態的一種觀察者模式的實現,經過讓View與ViewModel層可觀察的對象(好比LiveData)進行綁定,當ViewModel層數據發生變化,View層也會自動進行UI的更新,此種場景稱之爲單向綁定。

但實際開發中,單向綁定並不能足夠全部的需求。例若有下面的場景:若是佈局中有一個EditText,當用戶在輸入框中輸入內容時,咱們但願對應的Model類可以實時更新,這就須要雙向綁定,DataBinding一樣支持這樣的能力。
在這裏插入圖片描述
實現雙向綁定須要用到ObservableField類,它可以將普通的數據對象包裝成一個可觀察的數據對象,數據能夠是基本類型變量、集合,也能夠是自定義類型。爲了實現雙向綁定,咱們須要先定義一個繼承自BaseObservable的ViewModel類,並提供get和set方法,以下所示。

public class UserViewModel extends BaseObservable {

    private String name;
    private String address;
    private int age;

    @Bindable
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    ... //省略其餘代碼
}

而後,咱們在XML佈局文件中使用DataBinding,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="userModel"
            type="com.xzh.jetpack.UserViewModel" />
    </data>

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

        <EditText
            android:id="@+id/et1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@={userModel.name}" />

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="30dp"
            android:paddingRight="30dp"
            android:textSize="24dp"
            android:text="保存" />

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24dp"
            android:text="@={userModel.name}" />

    </LinearLayout>
</layout>

須要注意的是,不一樣於單向綁定,以前的佈局表達式是@{}表達式,雙向綁定使用的佈局表達式是@={},多了一個等號。

接下來,咱們在Activity中添加獲取用戶輸入的邏輯,以下所示。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main);
        activityMainBinding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name=activityMainBinding.et1.getText().toString();
                UserViewModel viewModel=new UserViewModel();
                viewModel.setName(name);
                activityMainBinding.setUserModel(viewModel);
            }
        });
    }
}

通過上面的處理後,雙向綁定就基本完成了。能夠發現,雙向綁定與LiveData很是類似,都是將普通的數據對象封裝成了可觀察對象,理論上兩者是能夠互相替代的,但LiveData具備生命週期感知能力,而且須要調用observe()方法進行監聽,而雙向綁定中更推薦使用ObservableField,不須要使用observe()方法,維護起來相對簡單。

5、在RecyclerView中使用DataBinding

5.1 基本使用

列表佈局在Android應用開發中是很是常見的場景,實現列表佈局須要使用RecyclerView控件,DataBinding支持在RecyclerViieew中實現數據綁定。

使用RcyclerView,就須要用到Adapter,在Adapter中實例化Item佈局,而後將List中的數據綁定到佈局中,而DataBinding就能夠幫助開發者實例化佈局並綁定數據。

首先,咱們編寫Adapter的item佈局,在item佈局中使用DataBinding將User數據進行綁定,item_user.xml的代碼以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">

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

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

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

</layout>

接下來,編寫Adapter類業務處理,UserAdapter的代碼以下所示。

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

    private List<UserModel> mDataList;

    public UserAdapter(List<UserModel> mDataList) {
        this.mDataList = mDataList;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        ItemUserBinding binding;
        public ViewHolder(@NonNull ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding=(ItemUserBinding)binding;
        }

        public ItemUserBinding getBinding() {
            return binding;
        }
    }


    @NonNull
    @Override
    public UserAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemUserBinding binding= DataBindingUtil.inflate((LayoutInflater.from(parent.getContext())), R.layout.item_user,parent,false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) {
        UserModel model=mDataList.get(position);
        holder.getBinding().setUser(model);
    }

    @Override
    public int getItemCount() {
        if (mDataList.size()>0){
            return  mDataList.size();
        }
        return 0;
    }
}

能夠發現,以前咱們都是須要在ViewHolder中進行findViewById對子控件進行實例化,因爲咱們使用了DataBinding,因此再也不須要這些操做,只須要傳入生成的Binding類便可,而後在super中調用getRoot()方法返回根View。

在RecyclerView中使用DataBinding就是如此簡單,當List中的item數據發生變化時,列表中的內容也會隨之更新。

而後,按照RecyclerView的基本使用方法,咱們在MainActivity添加一些測試數據,並將它和UserAdapter進行綁定,代碼以下。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;
    private List<UserModel> userModels;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        initData();
        initRecyclerView();
    }

    private void initData() {
        userModels = new ArrayList<UserModel>();
        for (int i = 0; i < 10; i++) {
            UserModel userModel = new UserModel("zhangsan"+1, "beijing"+i, "age"+i);
            userModels.add(userModel);
        }
    }


    private void initRecyclerView() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        activityMainBinding.recycle.setLayoutManager(layoutManager);
        activityMainBinding.recycle.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        UserAdapter adapter = new UserAdapter(userModels);
        activityMainBinding.recycle.setAdapter(adapter);
    }
}

5.2 自定義BindingAdapter

在上面的代碼中,對RecyclerView設置LayoutManager和Adapter屬於對View的一些複雜操做,這些操做能夠經過自定義BindingAdapter的方式進行簡化。首先,定義一個新的屬性,將數據List直接經過DataBinding在佈局文件中綁定,而且將這些操做都封裝到BindindAdapter中,Activity中再也不須要設置LayoutManager和Adapter操做。

首先,定義BindingAdapter,以下所示。

public class UserBindingAdapter {

    @BindingAdapter("users")
    void setUsers(RecyclerView recyclerView, List<UserModel> users )  {
        LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        UserAdapter adapter = new UserAdapter(users);
        recyclerView.setAdapter(adapter);
    }
}

在上面的代碼中,咱們聲明瞭一個新的屬性users,而後使用@BindingAdapter修飾靜態方法,而後在方法裏面對RecyclerView設置LayoutManager和Adapter。接下來,咱們只須要佈局中使用DataBinding便可。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <import type="java.util.List" />
        <variable
            name="users"
            type="List&lt;UserModel&gt;" />

    </data>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:users="@{users}"/>

</layout>

參考:
Android Jetpack架構組件(七)之WorkManager
Android Jetpack架構組件(六)之Room
Android Jetpack架構組件(五)之Navigation
Android Jetpack架構組件(四)之LiveData
Android Jetpack架構組件(三)之ViewModel
Android Jetpack架構組件(二)之Lifecycle
Android Jetpack架構組件(一)與AndroidX

相關文章
相關標籤/搜索