banner
jzman

jzman

Coding、思考、自觉。
github

Android Jetpack元件之DataBinding篇

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

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

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

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 屬性相較較,這種配置方式會在編譯時進行相關處理,如果該方法不存在或該方法簽名不正確,則會收到編譯時錯誤。首先創建一個事件處理方法如下:

/**
 * 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>

其他#

在 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 的介紹。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。