天天看點

Data Binding 詳解(四)-生成的綁定類

知是行之始,行是知之成。

文章配套的 Demo:https://github.com/muyi-yang/DataBindingDemo

Demo 支援 Java 和 Kotlin 雙語言,master 分支為 Java 語言代碼,kotlin 分支為 Kotlin 語言代碼。

Data Binding 生成用于通路布局變量和視圖的綁定類,它将布局變量與布局中的視圖連結起來。預設情況下,類的名稱基于布局檔案的名稱,将其轉換為Pascal大小寫并向其添加Binding字尾。比如布局檔案名是 activity_main.xml 相應生成的類 ActivityMainBinding,也可以自定義綁定類的名稱和包。 所有以

<data>

為根标簽的布局都會生成綁定類,都繼承自

ViewDataBinding

類。這個類包含從布局屬性(例如,聲明的變量)到布局視圖的所有綁定,并且知道如何為綁定表達式配置設定值,有興趣的同學可以看看生成後的類是怎麼實作的。

建立綁定對象

建立綁定類的對象有兩種方式,一種是使用

DataBindingUtil

工具類,一種是直接使用綁定類的靜态方法來擷取。

在 Activity 中我們一般使用 DataBindingUtil 工具類來擷取綁定對象,因為它有一個 setContentView 方法,裡面調用了 Activity 的 setContentView 方法并傳回了綁定類對象,比如:

也可以通過 DataBindingUtil 工具類的 inflate 方法擷取:

但在其他地方(Fragment、ListView 或 RecyclerView 等)我們一般會直接使用綁定類的靜态方法來擷取,比如:

LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater);
// 或者
LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater, viewGroup, false);
           

有時你可能需要使用老方式 inflate 一個布局,你可以這樣擷取綁定對象:

View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot)
           

有時無法事先知道綁定類型,布局可能是動态的。 在這種情況下,可以使用 DataBindingUtil 類建立綁定,如下面的代碼片段所示:

View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
ViewDataBinding binding = DataBindingUtil.bind(viewRoot)
// 或者
ViewDataBinding binding = DataBindingUtil.inflate(getLayoutInflater(), layoutId, parent, attachToParent);
           

至此我們了解了兩個擷取綁定類對象的兩種方式,可以根據不同的場景運用不同的方式,其實綁定類中的 inflate 方法的最終也是調用 DataBindingUtil 的 inflate 方法,bind 方法也是一樣,有興趣的同學可以閱讀一下生成的綁定類代碼。

帶 ID 的 View

Data Binding 在綁定類中為每個在布局中具有 ID 的 View 建立不可變字段。例如,Data Binding 從以下布局建立類型為 TextView 的 tvInfo,類型為 Button 的 btnBinding 字段:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
     ...
    <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv_info"
            ... />
        <Button
            android:id="@+id/btn_binding"
            ... />
        <Button
            android:id="@+id/btn_list"
            ... />
    </android.support.constraint.ConstraintLayout>
</layout>
           

在綁定類中已經生成了相應的字段,我們不在需要調用 findViewById () 方法擷取,可以直接從綁定類中擷取:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.tvInfo.setText("我是使用Data Binding的Demo");
           

變量

Data Binding 為布局中聲明的每個變量生成通路方法。 例如,下面的布局在綁定類中為 user、 index 變量生成 setter 和 getter 方法:

<!--activity_user.xml-->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/tools">
    <data>
        <import type="com.example.databindingdemo.bean.UserInfo" />
        ...
        <variable
            name="user"
            type="UserInfo" />

        <variable
            name="index"
            type="int" />
     ...
    </data>
    ...
</layout>
           

在擷取了綁定類對象的地方可以通過 setter 方法設定資料,通過 getter 方法擷取資料:

public class UserActivity extends AppCompatActivity {

    private ActivityUserBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
        ...
        binding.setUser(info);
        binding.setIndex(1);
        ...
    }
}
           

ViewStub的使用

ViewStub 與普通 View 不同,它開始時是一個不可見的 View。 當設定可見或者調用 inflate() 方法時,它們會在布局中通過 inflate 另一個布局來替換自己。

因為 ViewStub 中的布局實際上在 View 層次結構中并沒有加載,是以在綁定對象中也不能直接建立 ViewStub 中的布局對象,必須要在使用的時候再建立,是以綁定類中使用 ViewStubProxy 對象取代了 ViewStub,你可以使用它來通路 ViewStub,當 ViewStub 被建立和加載後你可以通過它來通路具體的布局結構。以下為布局:

<!--activity_binding.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">
    ...
        <ViewStub
            android:id="@+id/vs_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout="@layout/layout_bar" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{()->activity.showViewStub()}"
            android:text="@string/show_stub" />
    ...
</layout>

<!--layout_bar.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>
        <variable name="resId" type="int" />
        <variable name="name" type="String" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/default_mini_avatar"
            app:image="@{resId}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:gravity="center"
            android:text="@{name, [email protected]/default_name}"
            android:textSize="20sp" />
    </LinearLayout>
</layout>
           

這裡在布局中聲明了一個 ViewStub,并為它設定 layout_bar.xml 布局,同時聲明了一個 Button 并為它設定了點選事件(調用 showViewStub() 方法)。

當你想使用 ViewStub 中的布局時,你需要擷取到裡面的綁定類對象,你可以向 ViewStubProxy 設定一個 OnInflateListener 監聽器,然後在監聽器回調中擷取綁定類。比如:

public class BindingClassActivity extends AppCompatActivity {
    private ActivityBindingBinding binding;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_binding);
        binding.vsBar.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                LayoutBarBinding vsBarBinding = DataBindingUtil.bind(inflated);
                vsBarBinding.setName("木易");
                vsBarBinding.setResId(R.drawable.head);
            }
        });
       ...
    }
    ...
    public void showViewStub() {
        ViewStub viewStub = binding.vsBar.getViewStub();
        if (viewStub != null) {
            viewStub.inflate();
        }
    }
    ...
}
           

可以看到 showViewStub() 方法中調用 ViewStub 的 inflate() 方法,當 Button 被點選時就會觸釋出局的加載,加載完成後會觸發 OnInflateListener 的回調,然後在回調方法中通過

DataBindingUtil.bind(inflated)

擷取到了 ViewStub 中的布局綁定類,繼而進行資料綁定。

立即綁定

當變量或 observable 對象資料發生變化時,資料綁定将在 View 的下一幀重新整理之前更改。 但是,有時必須立即執行資料綁定。 若要強制執行,可以使用 executePendingBindings ()方法。一般情況不需要這麼做。

動态變量

有時候你的布局檔案是動态的,比如在一個 RecyclerView 中展現一系列新聞内容,有些内容是帶圖檔的,有些是純文字,這時我們可以采用多個 item 布局檔案來呈現不一樣的 UI 效果。比如:

public class NewsAdapter extends RecyclerView.Adapter {
    ...
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
        ViewDataBinding binding;
        if (viewType == VIEW_TYPE_TEXT) {
            binding = LayoutNewsItemTextBinding.inflate(inflater, viewGroup, false);
        } else {
            binding = LayoutNewsItemPictureBinding.inflate(inflater, viewGroup, false);
        }
        return new NewsViewHolder(binding.getRoot(), binding);
    }
    ...
}
           

這裡通過類型判斷,分别使用了

layout_news_item_text.xml

layout_news_item_picture.xml

布局檔案。這兩個布局檔案 UI 展現不一樣,但需要的資料類型都是 NewsInfo:

<!--layout_news_item_picture.xml-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="info"
            type="com.example.databindingdemo.bean.NewsInfo" />
    </data>
    ...
</layout>

<!--layout_news_item_text.xml-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="info"
            type="com.example.databindingdemo.bean.NewsInfo" />
    </data>
    ...
</layout>
           

這種情況下我們在 RecyclerView.Adapter 的 onBindViewHolder() 方法中就無法準确的知道綁定類類型,但是我們任然要為其綁定資料,我們可以這樣做:

public class NewsAdapter extends RecyclerView.Adapter {
    private List<NewsInfo> data = new ArrayList<>();
    ...
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
        NewsViewHolder holder = (NewsViewHolder) viewHolder;
        ViewDataBinding binding = holder.binding;
        binding.setVariable(BR.info, data.get(position));
    }
    ...
}
           

在這裡我們并沒有擷取具體的綁定類,而是擷取了 ViewDataBinding 類,它是一個抽象類,是所有綁定類的父類。它提供了一個

setVariable(int variableId, Object value)

方法(第一個參數為布局中聲明的綁定變量 ID,第二個參數為要綁定的資料),通過這個方法我們可以動态的為布局中的變量綁定相應的資料。

BR 是 Data Binding 自動生成的資源 ID 檔案,它包含所有的資料綁定變量的 ID,類似于 Android 的 R 檔案。在上面例子中,RB.info 是布局中 info 變量的 ID。

背景線程

你可以在背景線程中更改資料,隻要它不是一個集合。Data Binding 會在計算時将每個變量/字段在各個線程中做一份資料拷貝,以避免同步問題。

自定義綁定類名稱

預設情況下 Data Binding 将根據布局檔案的名稱生成綁定類,以大寫字母開頭,删除下劃線,駝峰格式命名,并添加 Binding 字尾。該類放在子產品包下的 databinding 包中。例如,布局檔案 layout_custom.xml 生成 LayoutCustomBinding類,放在 com.example.databindingdemo.databinding包中。

通過調整 data 标簽的 class 屬性,可以重命名綁定類或将綁定類放在不同的包中。例如,以下布局在目前子產品的 databinding 包中生成綁定類 MyCustom:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data class="MyCustom">
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>
           

效果圖:

Data Binding 詳解(四)-生成的綁定類

你可以通過在類名前加一個句點來使生成綁定類在子產品包中:

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

效果圖:

Data Binding 詳解(四)-生成的綁定類

你還可以在類名前使用完整的包名。下面的示例在 com.custom 包中建立 MyCustom 綁定類:

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

效果圖:

Data Binding 詳解(四)-生成的綁定類

此篇到這裡就結束了,可以檢視下一篇 Data Binding 詳解(五)-綁定擴充卡。

如果你覺得文章有幫助到你,記得點個喜歡以表支援,同時歡迎你的指正和建議。十分感謝!