知是行之始,行是知之成。
文章配套的 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>
效果圖:
你可以通過在類名前加一個句點來使生成綁定類在子產品包中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".MyCustom">
</data>
...
</layout>
效果圖:
你還可以在類名前使用完整的包名。下面的示例在 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 詳解(五)-綁定擴充卡。
如果你覺得文章有幫助到你,記得點個喜歡以表支援,同時歡迎你的指正和建議。十分感謝!