知是行之始抬驴,行是知之成筒占。
文章配套的 Demo:https://github.com/muyi-yang/DataBindingDemo
Demo 支持 Java 和 Kotlin 雙語言,master 分支為 Java 語言代碼淹冰,kotlin 分支為 Kotlin 語言代碼峭火。
Data Binding 生成用于訪問布局變量和視圖的綁定類,它將布局變量與布局中的視圖鏈接起來牍鞠。默認情況下,類的名稱基于布局文件的名稱评姨,將其轉(zhuǎn)換為Pascal大小寫并向其添加Binding后綴难述。比如布局文件名是 activity_main.xml 相應生成的類 ActivityMainBinding,也可以自定義綁定類的名稱和包吐句。 所有以 <data>
為根標簽的布局都會生成綁定類胁后,都繼承自 ViewDataBinding
類。這個類包含從布局屬性(例如嗦枢,聲明的變量)到布局視圖的所有綁定攀芯,并且知道如何為綁定表達式分配值,有興趣的同學可以看看生成后的類是怎么實現(xiàn)的文虏。
創(chuàng)建綁定對象
創(chuàng)建綁定類的對象有兩種方式侣诺,一種是使用 DataBindingUtil
工具類,一種是直接使用綁定類的靜態(tài)方法來獲取氧秘。
在 Activity 中我們一般使用 DataBindingUtil 工具類來獲取綁定對象年鸳,因為它有一個 setContentView 方法,里面調(diào)用了 Activity 的 setContentView 方法并返回了綁定類對象敏储,比如:
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
也可以通過 DataBindingUtil 工具類的 inflate 方法獲茸栊恰:
ActivityMainBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_main, null, false);
但在其他地方(Fragment、ListView 或 RecyclerView 等)我們一般會直接使用綁定類的靜態(tài)方法來獲取已添,比如:
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)
有時無法事先知道綁定類型滥酥,布局可能是動態(tài)的更舞。 在這種情況下,可以使用 DataBindingUtil 類創(chuàng)建綁定坎吻,如下面的代碼片段所示:
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
ViewDataBinding binding = DataBindingUtil.bind(viewRoot)
// 或者
ViewDataBinding binding = DataBindingUtil.inflate(getLayoutInflater(), layoutId, parent, attachToParent);
至此我們了解了兩個獲取綁定類對象的兩種方式缆蝉,可以根據(jù)不同的場景運用不同的方式,其實綁定類中的 inflate 方法的最終也是調(diào)用 DataBindingUtil 的 inflate 方法,bind 方法也是一樣刊头,有興趣的同學可以閱讀一下生成的綁定類代碼黍瞧。
帶 ID 的 View
Data Binding 在綁定類中為每個在布局中具有 ID 的 View 創(chuàng)建不可變字段。例如原杂,Data Binding 從以下布局創(chuàng)建類型為 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>
在綁定類中已經(jīng)生成了相應的字段,我們不在需要調(diào)用 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 方法設置數(shù)據(jù),通過 getter 方法獲取數(shù)據(jù):
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僵朗。 當設置可見或者調(diào)用 inflate() 方法時,它們會在布局中通過 inflate 另一個布局來替換自己屑彻。
因為 ViewStub 中的布局實際上在 View 層次結(jié)構(gòu)中并沒有加載衣迷,所以在綁定對象中也不能直接創(chuàng)建 ViewStub 中的布局對象,必須要在使用的時候再創(chuàng)建酱酬,所以綁定類中使用 ViewStubProxy 對象取代了 ViewStub壶谒,你可以使用它來訪問 ViewStub,當 ViewStub 被創(chuàng)建和加載后你可以通過它來訪問具體的布局結(jié)構(gòu)膳沽。以下為布局:
<!--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, default=@string/default_name}"
android:textSize="20sp" />
</LinearLayout>
</layout>
這里在布局中聲明了一個 ViewStub汗菜,并為它設置 layout_bar.xml 布局,同時聲明了一個 Button 并為它設置了點擊事件(調(diào)用 showViewStub() 方法)挑社。
當你想使用 ViewStub 中的布局時陨界,你需要獲取到里面的綁定類對象,你可以向 ViewStubProxy 設置一個 OnInflateListener 監(jiān)聽器痛阻,然后在監(jiān)聽器回調(diào)中獲取綁定類菌瘪。比如:
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() 方法中調(diào)用 ViewStub 的 inflate() 方法,當 Button 被點擊時就會觸發(fā)布局的加載阱当,加載完成后會觸發(fā) OnInflateListener 的回調(diào)俏扩,然后在回調(diào)方法中通過 DataBindingUtil.bind(inflated)
獲取到了 ViewStub 中的布局綁定類,繼而進行數(shù)據(jù)綁定弊添。
立即綁定
當變量或 observable 對象數(shù)據(jù)發(fā)生變化時录淡,數(shù)據(jù)綁定將在 View 的下一幀刷新之前更改。 但是油坝,有時必須立即執(zhí)行數(shù)據(jù)綁定嫉戚。 若要強制執(zhí)行刨裆,可以使用 executePendingBindings ()方法。一般情況不需要這么做彬檀。
動態(tài)變量
有時候你的布局文件是動態(tài)的帆啃,比如在一個 RecyclerView 中展現(xiàn)一系列新聞內(nèi)容,有些內(nèi)容是帶圖片的窍帝,有些是純文本努潘,這時我們可以采用多個 item 布局文件來呈現(xiàn)不一樣的 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 展現(xiàn)不一樣,但需要的數(shù)據(jù)類型都是 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() 方法中就無法準確的知道綁定類類型拥峦,但是我們?nèi)稳灰獮槠浣壎〝?shù)據(jù)贴膘,我們可以這樣做:
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)
方法(第一個參數(shù)為布局中聲明的綁定變量 ID玄柠,第二個參數(shù)為要綁定的數(shù)據(jù))突梦,通過這個方法我們可以動態(tài)的為布局中的變量綁定相應的數(shù)據(jù)。
BR 是 Data Binding 自動生成的資源 ID 文件羽利,它包含所有的數(shù)據(jù)綁定變量的 ID宫患,類似于 Android 的 R 文件。在上面例子中这弧,RB.info 是布局中 info 變量的 ID娃闲。
后臺線程
你可以在后臺線程中更改數(shù)據(jù),只要它不是一個集合匾浪。Data Binding 會在計算時將每個變量/字段在各個線程中做一份數(shù)據(jù)拷貝皇帮,以避免同步問題。
自定義綁定類名稱
默認情況下 Data Binding 將根據(jù)布局文件的名稱生成綁定類蛋辈,以大寫字母開頭属拾,刪除下劃線,駝峰格式命名冷溶,并添加 Binding 后綴渐白。該類放在模塊包下的 databinding 包中。例如挂洛,布局文件 layout_custom.xml 生成 LayoutCustomBinding類礼预,放在 com.example.databindingdemo.databinding包中。
通過調(diào)整 data 標簽的 class 屬性虏劲,可以重命名綁定類或?qū)⒔壎惙旁诓煌陌型兴帷@纾韵虏季衷诋斍澳K的 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 包中創(chuàng)建 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>
效果圖:此篇到這里就結(jié)束了励堡,可以查看下一篇 Data Binding 詳解(五)-綁定適配器。
如果你覺得文章有幫助到你堡掏,記得點個喜歡以表支持应结,同時歡迎你的指正和建議。十分感謝泉唁!