Android DataBinding (一) 基本用法 (本文)
Android DataBinding (二) 事件處理
Android DataBinding (三) Observable
Android DataBinding (四) 自定義屬性
Android DataBinding (五) 自定義 View 的雙向綁定
Android DataBinding (六) EditText 綁定 TextChangedListener 和 FocusChangeListener
概述
2015 年的 I/O 大會(huì)上坯约,Google 發(fā)布了官方的數(shù)據(jù)綁定框架 Data Binding Library壶辜,使得官方原生支持 MVVM。
Data Binding 是把數(shù)據(jù)直接綁定到 XML 文件上,并能實(shí)現(xiàn)自動(dòng)刷新。
Data Binding 減少了代碼的耦合性,一些如 findViewById试伙、setText 之類的操作都可以通過綁定實(shí)現(xiàn)。
環(huán)境配置
環(huán)境配置非常簡(jiǎn)單于样,只要在 build.gradle 文件里面定義一下代碼即可
android {
....
dataBinding {
enabled = true
}
}
一個(gè)簡(jiǎn)單的例子
- 首先疏叨,定義一個(gè) Java Bean
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
- 然后定義 Layout 文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.tianjf.myapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(user.age)}" />
</LinearLayout>
</layout>
Layout 文件和之前有什么不同呢?
首先穿剖,在最外層再套一層 <layout></layout> 標(biāo)簽蚤蔓。
然后,在 layout 標(biāo)簽下面定義 <data></data> 標(biāo)簽糊余。
data 標(biāo)簽下面的 variable 定義數(shù)據(jù)綁定用的實(shí)體類秀又。這個(gè)實(shí)體是從外部傳進(jìn)來的(具體怎么傳下文再講)。
type 里面是完整的帶包名的類贬芥,
name 自定義一個(gè)名稱吐辙,下面具體綁定的時(shí)候就是用的這個(gè)名稱。
最后蘸劈,用 @{} 來把數(shù)據(jù)綁定到 UI 上昏苏。
@{user.name} 把 name 屬性綁定到第一個(gè) TextView 上。
@{String.valueOf(user.age)} 把 age 屬性綁定到第二個(gè) TextView 上威沫。這里 age 是 int 類型的捷雕,所以需要把它轉(zhuǎn)化成 String 類型。由于 String 是屬于 java.lang 下面的壹甥,所以不需要 import救巷。java.lang 以外的類是需要 import 的(具體怎么 import 下文再講)。
- 數(shù)據(jù)綁定
之前的 Layout 文件的定義會(huì)默認(rèn)生成一個(gè)數(shù)據(jù)綁定類句柠,這個(gè)數(shù)據(jù)綁定類的名稱默認(rèn)和 Layout 文件的類名有關(guān)浦译。比如 activity_main.xml 會(huì)生成 ActivityMainBinding.java 文件。
我們來看看代碼
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setUser(new User("Jack", 10));
}
}
用 DatabindingUtil.setContentView() 替換之前的 setContentView()溯职,返回值是自動(dòng)生成的 ActivityMainBinding精盅,然后調(diào)用 setUser 方法把 User 實(shí)例綁定到 XML 文件中去。
這樣谜酒,運(yùn)行之后就可以看到 User 的信息被顯示到了畫面上了叹俏。
生成的 Binding 類的獲取方式
上面例子中由于是 Activity 的 Layout 文件,所以使用了 DataBindingUtil.setContentView 來獲取僻族。
除了上面的方法粘驰,還可以通過 inflate 獲取屡谐。
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果是在 ListView 或者 RecyclerView 的 adapter 中
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
控制自動(dòng)生成類的生成方式
默認(rèn)情況下,會(huì)根據(jù) XML 文件的名稱(具體規(guī)則上文已經(jīng)提及)在 <package name>.databinding 目錄下生成文件蝌数。比如 package 名為 com.example.myapplication愕掏,那么會(huì)在 com.example.myapplication.databinding 下面生成文件。
生成的文件的名稱也可以自定義顶伞。
<data class="MainBinding">
...
</data>
這樣的話饵撑,就會(huì)在 com.example.myapplication.databinding 下面生成 MainBinding 的文件。
生成的文件的路徑也可以自定義唆貌。
如果想直接在 package 下面生成
<data class=".MainBinding">
...
</data>
加一個(gè) . 就會(huì)在 com.example.myapplication 下面生成 MainBinding 的文件滑潘。
當(dāng)然,不想使用 package 名锨咙,想自己自定義路徑语卤,也是可以的,寫全你想要的路徑即可
<data class="com.example.MainBinding">
...
</data>
import
當(dāng)在 XML 中數(shù)據(jù)綁定的時(shí)候蓖租,用到了 java.lang 之外的類粱侣,必須在 data 標(biāo)簽下面 import羊壹。
比如想控制 View 的顯示和隱藏蓖宦。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:visibility="@{user.showName ? View.VISIBLE : View.GONE}" />
由于用到了 View 類,所以必須 import油猫。
<data>
<import type="android.view.View" />
......
</data>
variable 中用到的類也可以先 import 在使用稠茂,其實(shí)和 Java 是一樣的。
<data>
<import type="com.example.tianjf.myapplication.User" />
<variable
name="user"
type="User" />
</data>
如果類名相同情妖,package 名不相同睬关,上面的寫法就會(huì)出現(xiàn) type 不知道指定哪個(gè)類。但是別擔(dān)心毡证,可以用別名解決电爹。
<data>
<import type="com.example.tianjf.myapplication.User" alias="User1" />
<import type="com.example.tianjf.User" alias="User2" />
<variable
name="user1"
type="User1" />
<variable
name="user2"
type="User2" />
</data>
import 的類型可以用到 variable 中
<data>
<import type="com.example.tianjf.myapplication.User" />
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
import 的類型可以用到表達(dá)式中
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
import 的類型可以調(diào)用它的 static 變量和 static 方法
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
include
當(dāng) layout 文件中用到 include 的時(shí)候,variable 也可以傳到 include 的 layout 文件中繼續(xù)使用
<?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.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
Null Safe
DataBinding 是 Null Safe 的料睛,比如下列代碼
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
如果 user 為 null丐箩,@{user.name} 也將為 null,并不會(huì)出現(xiàn) NullPointerException恤煞。
表達(dá)式的使用
DataBinding 的時(shí)候可以指定綁定傳進(jìn)來的值屎勘,也可以使用表達(dá)式達(dá)到各種效果!
前面提到的控制 View 的顯示和隱藏
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:visibility="@{user.showName ? View.VISIBLE : View.GONE}" />
?? 操作符
android:text="@{user.displayName ?? user.lastName}"
如果不為 null居扒,則選取 ?? 左邊的概漱,如果為 null,則選取 ?? 右邊的喜喂,相當(dāng)于以下代碼
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
集合的使用
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
</data>
…
android:text="@{list[0]}"
…
android:text="@{sparse[0]}"
…
android:text="@{map['key']}"
Resources 的使用
DataBinding 的時(shí)候也可以使用 resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
Format strings 也可以使用瓤摧。
比如下面的 string
<string name="name">My name is %s</string>
可以使用 String.format 傳入?yún)?shù)
android:text="@{String.format(@string/name,user.name)}"
也可以這樣寫
android:text="@{@string/name(user.name)}"
雙向綁定
前面介紹的都是單向綁定竿裂,即 ViewModel 的值綁定到 UI 上。如果希望 UI 的變更也反應(yīng)到 ViewModel姻灶,那么就需要雙向綁定了铛绰。其實(shí)雙向綁定很簡(jiǎn)單,只需要加個(gè) = 就好了产喉。
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.firstName}"/>