前面總結了 ViewModel、LiveData 及 Lifecycle 架構組件的使用,可先閱讀下面文章詳細了解:
本篇主要側重 dataBinding 的基本使用,主要內容如下:
- dataBinding 支持
- 佈局文件配置
- 綁定數據
- 特殊表達式
- 事件綁定
- 自定義綁定類
- 其他
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<String,String>" />
<variable
name="key"
type="String" />
<!--List-->
<variable
name="list"
type="List<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 的介紹。