Android Jetpack組件之DataBinding詳解

原文首發於微信公衆號:躬行之(jzman-blog)

前面總結了 ViewModel、LiveData 及 Lifecycle 架構組件的使用,可先閱讀下面文章詳細瞭解:

本篇主要側重 dataBinding 的基本使用,主要內容如下:

  1. dataBinding支持
  2. 佈局文件配置
  3. 綁定數據
  4. 特殊表達式
  5. 事件綁定
  6. 自定義綁定類
  7. Others

dataBinding支持

使用 dataBinding 需要在 app module 下面的 build.gradle 文件中進行配置,具體如下:

//設置支持dataBinding   
dataBinding {
    enabled = true
}

佈局文件配置

Data Binding Library 會自動生成將佈局中的視圖和數據對象綁定所需要的類,Data Binding Library 的佈局文件中以 layout 標籤爲根標籤,然後是具體的數據元素和視圖元素,此視圖元素是綁定佈局文件的位置,佈局文件參考如下:

<?xml version="1.0" encoding="utf-8"?>
<!--dataBinding必須以layout作爲根標籤-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--數據對象-->
    <data>
        <variable name="user" type="com.manu.databindsample.data.User"/>
    </data>
    <!--視圖元素-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--dataBinding中具體屬性值的配置在"@{}"中進行配置-->
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=姓名}"/>
    </LinearLayout>
</layout>

數據實體

在 “@{user.name}” 中的 name 屬性最終映射調用數據對象的 getter 方法,也就是 getter 方法,當然,如果數據對象中有對應的 name 方法,在沒有與之對應 getter 方法的時候會調用與之同名的方法,如果兩者都存在,則會優先調用與之對應的 getter 方法,參考如下:

/**
 * 數據實體
 * Powered by jzman.
 * Created on 2018/11/28 0028.
 */
public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
    
    //兩者存在優先調用
    public String getName() {
        return name;
    }
    //getter方法不存在會調用
    public String name() {
        return "name";
    }

    //...
}

綁定數據

dataBinding 會爲內個佈局文件生成對應的綁定類,默認情況下,類的名稱基於佈局文件的名稱,如佈局文件名爲 activity_main,則該佈局文件對應的綁定類是 ActivityMainBinding,該類包含數據對象到佈局文件的所有綁定,那麼如何綁定數據和視圖呢,在 Activty 中綁定方式如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //生成綁定類
        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        //綁定視圖與數據
        User user = new User("張三");
        binding.setUser(user);
    }
}

在 Fragment 中綁定方式如下:

//inflate方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
    User user = new User("小明");
    oneBinding.setUser(user);
    return oneBinding.getRoot();
}
//bind方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    View view = inflater.inflate(R.layout.fragment_one,container,false);
    FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
    User user = new User("小明");
    oneBinding.setUser(user);
    return view;
}

其他佈局的綁定方式基本是都是使用某個生成的綁定類的 inflate 方法和 bind 方法就可以完成。

特殊表達式

  • 三目運算符簡化
//完整寫法
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
//簡寫
android:text="@{user.displayName ?? user.lastName}"
  • 空指針異常處理

生成的綁定類會自動檢查 null 值以避免 NullPointerException,在表達式 @ {user.name} 中,如果 user 爲 null,則爲user.name 分配其默認值 null。 如果引用 user.age,其中 age 的類型爲 int,則數據綁定使用默認值0。

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

    <data>

        <import type="java.util.Map" />

        <import type="java.util.List" />

        <!--Map-->
        <variable
            name="map"
            type="Map&lt;String,String>" />

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

        <!--List-->
        <variable
            name="list"
            type="List&lt;String>" />

        <variable
            name="index"
            type="int" />
    </data>
    <!--注意Map和List取值方式-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list[0]}" />

    </LinearLayout>
</layout>

對於 Map 類型的數據可以在表達式 @{} 中使用 map.key 來獲取 Map 集合中 key 對應的 value 值,List 類型的數據直接使用索引來取值,此外在 variable 標籤中使用到的 < 要進行轉義,及使用 < 來代替 <,否則報錯如下:

> Error: 與元素類型 "variable" 相關聯的 "type" 屬性值不能包含 '<' 字符。
  • @{} 表達式中使用字符串

如何在 @{} 表達式中使用字符串而不是字符串變量呢,有兩種方式,具體如下:

<!--使用單引號-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{map["key"]}' />
<!--使用後引號-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{map[`key`]}" />
<!--在@{}中可以使用字符串資源-->
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{@string/app_name}"/>

事件綁定

使用 databinding 時可以採用方法引用或監聽綁定的方式來設置事件監聽,這兩者的區別是前者的事件監聽器是在數據綁定時創建的,而後者是在事件觸發時綁定。

  • 方法引用

事件可以直接綁定在事件處理方法上,與普通的 android:onClick 屬性相比較,這種配置方式會在編譯時進行相關處理,如果該方法不存在或該方法簽名不正確,則會收到編譯時錯誤。首先創建一個事件處理方法如下:

/**
 * Powered by jzman.
 * Created on 2018/11/30 0030.
 */
public class MyHandler {
    /**
     * @param view
     */
    public void onClickEvent(View view){
        Toast.makeText(view.getContext(), "click me", Toast.LENGTH_SHORT).show();
    }
}

然後,在對應的佈局文件中配置具體的 onClick 等事件,這裏以 onClick 事件爲例,具體如下:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--第一種方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler::onClickEvent}"/>
        <!--第二種方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler.onClickEvent}"/>
    </LinearLayout>
</layout>

最後,在對應的 Activity 中設置數據對象 handler 即可,具體參考如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setHandler(new MyHandler());
}

這樣通過方法引用事件就成功綁定了。

  • 監聽綁定

這種方式是在事件發生時創建事件監聽器,相較方法引用可以傳遞自定義參數在事件回調中,首先,創建一個事件回調方法如下:

/**
 * 監聽綁定
 * Powered by jzman.
 * Created on 2018/12/3 0003.
 */
public class MyPresenter {
    private Context mContext;

    public MyPresenter(Context mContext) {
        this.mContext = mContext;
    }

    public void onClickEvent(User user) {
        Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
    }
}

然後,在對應的佈局文件中配置具體的 onClick 等事件,這裏以 onClick 事件爲例,具體如下:

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

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />

        <variable
            name="presenter"
            type="com.manu.databindsample.handler.MyPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onClickEvent(user)}"
            android:text="click me 3" />
    </LinearLayout>
</layout>

最後,在對應的 Activity 中設置數據對象 presenter 即可,具體參考如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setUser(new User("android"));
    binding.setPresenter(new MyPresenter(this));
}

這樣通過事件監聽事件就成功綁定了,在上面 xml 中調用事件方法時,可以在配置當前 View,具體如下:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(view) -> presenter.onClickEvent(user)}"
    android:text="click me 3" />

則對應的事件回調方法如下:

public class MyPresenter {
    public void onClickEvent(View view, User user){}
}

此外,也可以在事件綁定時使用三目運算符,此時可將 void 作爲操作符使用,使用方式參考如下:

android:onClick="@{(v) -> v.isVisible() ? presenter.doSomething() : void}"

自定義綁定類

從前面可知,默認狀態下綁定類名稱是由佈局文件名稱決定,那麼如何自定義綁定類呢,在佈局文件 data 標籤上使用 class 屬性指定自定義的綁定類名即可,當然也可以在自定義類名前面添加完成的包路徑,參考如下:

<!--自定義綁定類-->
<data class="com.manu.test.CustomBinding">
    <variable name="user" type="com.manu.databindsample.data.User"/>
</data>

Others

在 databinding 中使用 import 關鍵字導入相關的類,java.lang.* 下面的相關類默認自動導入,如果有相同名字的 View 可以使用使用 alias 來區分,參考如下:

<import type="android.view.View"/>
<import type="com.manu.View"
        alias="MView"/>

使用 variable 關鍵字定義要在 xml 佈局中使用的變量,如果使用了 include 佈局,則要使用 bind 綁定 include 包含的佈局與主佈局使用同樣的變量,創建一個 include 包含的佈局 test_layout.xml 文件,具體如下:

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

    <data>
        <variable
            name="userTest"
            type="com.manu.databindsample.data.User" />
    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`this is include content...`+userTest.getName(),default=user}" />
    </LinearLayout>
</layout>

然後,在主佈局中引用這個佈局,具體如下:

<?xml version="1.0" encoding="utf-8"?>

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

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
         <!--bind綁定變量-->
        <include
            layout="@layout/test_layout"
            bind:userTest="@{user}" />
    </LinearLayout>
</layout>

這樣通過 bind 就綁定了兩個佈局中使用到的 User 類型的變量,使得兩個佈局中使用的變量是同一個變量,此外,databinding 不支持 merge 標籤,下篇繼續 Binding adapters 的介紹。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章