Android Jetpack成员之一:DataBinding
本篇文章将从以下几点介绍 DataBinding
:
-
环境配置
-
在
Activity
中使用DataBinding
-
在
Fragment
中使用DataBinding
-
在
RecyclerView
中使用DataBinding
-
在
XML
中使用表达式 -
在
XML
中直接绑定方法 -
结合可观察变量和可观察对象使用
DataBinding
正文开始,真香警告!(文章较长,建议准备一包瓜子…)
环境配置
使用 DataBinding
的前提需要完成以下两步:
-
在工程的
build.gradle
下开启DataBinding
选项:1android {
2 // 注意dataBinding的大小写啊!!!
3 dataBinding {
4 enabled = true
5 }
6} -
在工程的
gradle.properties
下启用新的数据绑定编译器:1android.databinding.enableV2=true
Activity 中使用 DataBinding
环境配置好了之后,我们就可以使用 DataBinding
了,首先就拿 Activity
开刀吧,毕竟是最熟悉的陌生人 ^ - ^
1)布局
1<?xml version="1.0" encoding="utf-8"?>
2
3<layout>
4 <data>
5 // UserBean为数据类
6 <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
7 // otherName仅仅是一个String对象
8 <variable name="otherName" type="String"/>
9 </data>
10
11 <androidx.constraintlayout.widget.ConstraintLayout
12 xmlns:android="http://schemas.android.com/apk/res/android"
13 xmlns:tools="http://schemas.android.com/tools"
14 xmlns:app="http://schemas.android.com/apk/res-auto"
15 android:layout_width="match_parent"
16 android:layout_height="match_parent"
17 tools:context=".data.binding.DataBindingActivity">
18
19 <TextView android:layout_width="match_parent"
20 android:layout_height="wrap_content"
21 android:id="@+id/tv_first"
22 android:text="@{userBean.firstName}"
23 android:textSize="20sp"
24 android:gravity="center"
25 app:layout_constraintTop_toTopOf="parent"
26 app:layout_constraintLeft_toLeftOf="parent"/>
27
28 <TextView android:layout_width="match_parent"
29 android:layout_height="wrap_content"
30 android:id="@+id/tv_last"
31 android:text="@{userBean.lastName + otherName}"
32 android:textSize="20sp"
33 android:gravity="center"
34 app:layout_constraintLeft_toLeftOf="parent"
35 app:layout_constraintTop_toBottomOf="@id/tv_first"/>
36
37 </androidx.constraintlayout.widget.ConstraintLayout>
38</layout>
看完上面的布局文件,发现比平时写布局文件多了一些东西,我们来一个一个观察:
-
XML
最外层必须由layout
标签包围; -
你需要使用什么样的数据,可以在
data
标签内通过variable
来导入; -
使用数据时,只需要通过
variable
的name
或者name
的属性来获取内容即可。
Tips:进行完这一步之后,一定要 make
一下工程,否则下一步无法进行
2)绑定布局make
工程之后,会自动为我们生成一个类,类名的规则是布局文件名 + Binding
,比如布局文件名为:activity_main
,那么生成的类名为:ActivityMainBinding
,这个类我们接下来会使用到,是绑定布局的关键之处。
1class DataBindingActivity : AppCompatActivity() {
2
3 override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 // 绑定布局从setContentView()变成了DataBindingUtil.setContentView()
6 val binding: ActivityDataBindingBinding = DataBindingUtil.setContentView(
7 this, R.layout.activity_data_binding
8 )
9 binding.userBean = UserBean("Activity", "Taonce")
10 binding.otherName = " Android"
11 }
12}
布局的绑定只是从 setContentView( laytouId )
换成了 DataBindingUtil.setContentView( activity, layoutId)
,而且这个方法会返回一个 ViewDataBinding
对象,这里的 ActivityDataBindingBinding
是 ViewDataBinding
子类,我们可以通过 ViewDataBinding
设置布局所需要的数据。比如这里想要设置 TextView
的 text
属性,不用再 setText( text )
了,只需要设置 binding.userBean
和 binding.otherName
就好了。
Fragment 中使用 DataBinding
知道了在 Activity
中如何使用 DataBinding
之后,Fragment
也是类似, 至少 XML
是不变的,我们只需要知道如何绑定就ok了:
1class DataBindingFragment : Fragment() {
2
3 override fun onCreateView(
4 inflater: LayoutInflater, container: ViewGroup?,
5 savedInstanceState: Bundle?
6 ): View? {
7 // Fragment中使用的是DataBindingUtil.inflate()来获取ViewDataBinding对象
8 val binding = DataBindingUtil.inflate<FragmentDataBindingBinding>(
9 inflater,
10 R.layout.fragment_data_binding,
11 container, false
12 )
13 binding.userBean = UserBean("Fragment", "")
14 return binding.root
15 }
16}
Fragment
中通过 DataBindingUtil.inflate()
来获取 ViewDataBinding
对象,然后返回 ViewDataBinding.root
,整个绑定就结束了!
Adapter 中使用 DataBinding
在 RecyclerView
中使用 DataBinding
稍微比上面两种要复杂一点点,但是聪明的你,肯定是扫一眼就懂了。
1)RecyclerView
和 Adapter
绑定:在 XML
中设置 adapter
属性就完事了~
1// XML
2<layout>
3
4 <data>
5 <variable name="adapter" type="com.taonce.myjetpack.data.binding.list.ListAdapter"/>
6 </data>
7
8 <androidx.constraintlayout.widget.ConstraintLayout
9 xmlns:android="http://schemas.android.com/apk/res/android"
10 xmlns:tools="http://schemas.android.com/tools"
11 xmlns:app="http://schemas.android.com/apk/res-auto"
12 android:layout_width="match_parent"
13 android:layout_height="match_parent"
14 tools:context=".data.binding.list.ListActivity">
15
16 <androidx.recyclerview.widget.RecyclerView
17 android:id="@+id/rcv"
18 android:layout_width="match_parent"
19 android:layout_height="match_parent"
20 android:adapter="@{adapter}"/>
21
22 </androidx.constraintlayout.widget.ConstraintLayout>
23</layout>
24
25// Activity
26binding.adapter = ListAdapter(
27 this, listOf(
28 UserBean("one", ""),
29 UserBean("two", ""),
30 UserBean("three", "")
31 )
32)
2)Item
的数据绑定:
生成 Item
布局文件:
1<layout>
2
3 <data>
4 <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
5 </data>
6
7 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
8 android:orientation="vertical"
9 android:layout_width="match_parent"
10 android:layout_height="wrap_content">
11
12 <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
13 android:id="@+id/tv_item"
14 android:textSize="20sp"
15 android:layout_gravity="center"
16 android:text="@{userBean.firstName}"
17 android:gravity="center"/>
18
19 </LinearLayout>
20</layout>
接下来需要在 Adapter
中将 Item
所需要的数据传进来:
1class ListAdapter(
2 private val context: Context,
3 val data: List<UserBean>
4) : RecyclerView.Adapter<ListAdapter.Holder>() {
5 // 绑定数据需要
6 lateinit var binding: ItemBinding
7
8 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
9 // 获取ViewDataBinding对象
10 binding = DataBindingUtil.inflate(
11 LayoutInflater.from(context),
12 R.layout.item, parent, false
13 )
14 return Holder(binding.root)
15 }
16
17 override fun getItemCount(): Int {
18 return data.size
19 }
20
21 override fun onBindViewHolder(holder: Holder, position: Int) {
22 // 根据position将数据和xml中的userBean绑定
23 binding.userBean = data[position]
24 // 立即改变数据
25 binding.executePendingBindings()
26 }
27
28 class Holder(val view: View) : RecyclerView.ViewHolder(view)
29}
其实上面的注释已经说明了一切,就是把 Holder
需要做个工作给省去了,通过 ViewDataBinding
对象进行数据绑定,不需要再手动的去 holder.tv.setText( text )
了。
DataBinding 中使用表达式
上面的案例一直都是 XML
中简单的绑定 text
属性,试想一下如果我们传入的值为空,那么岂不是很尴尬,所以 DataBinding
还给我们提供了多种表达式,以便更加灵活使用数据绑定,跟着例子一起来看看:
1<?xml version="1.0" encoding="utf-8"?>
2
3<layout>
4
5 <data>
6 <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
7 <variable name="age" type="Integer"/>
8 <variable name="view" type="android.view.View"/>
9 <variable name="ageLess20" type="String"/>
10 <variable name="ageThan20" type="String"/>
11 </data>
12
13 <androidx.constraintlayout.widget.ConstraintLayout
14 xmlns:android="http://schemas.android.com/apk/res/android"
15 xmlns:tools="http://schemas.android.com/tools"
16 xmlns:app="http://schemas.android.com/apk/res-auto"
17 android:layout_width="match_parent"
18 android:layout_height="match_parent"
19 tools:context=".data.binding.expression.ExpressionActivity">
20
21 <TextView android:layout_width="match_parent"
22 android:layout_height="wrap_content"
23 android:textSize="20sp"
24 android:textColor="@color/colorAccent"
25 android:padding="10dp"
26 android:id="@+id/tv_one"
27 app:layout_constraintLeft_toLeftOf="parent"
28 app:layout_constraintTop_toTopOf="parent"
29 android:text="@{userBean.firstName ?? userBean.lastName}"/>
30
31 <TextView android:layout_width="match_parent"
32 android:layout_height="wrap_content"
33 android:textSize="20sp"
34 android:textColor="@color/colorAccent"
35 android:padding="10dp"
36 android:id="@+id/tv_two"
37 app:layout_constraintTop_toBottomOf="@id/tv_one"
38 android:text='@{userBean.lastName.isEmpty() ? "lastName is null" : userBean.lastName}'/>
39
40 <TextView android:layout_width="match_parent"
41 android:layout_height="wrap_content"
42 android:textSize="20sp"
43 android:textColor="@color/colorAccent"
44 android:padding="10dp"
45 android:id="@+id/tv_three"
46 app:layout_constraintTop_toBottomOf="@id/tv_two"
47 android:text="@{ageThan20}"
48 android:visibility="@{age > 20 ? view.VISIBLE : view.GONE}"/>
49
50 <TextView android:layout_width="match_parent"
51 android:layout_height="wrap_content"
52 android:textSize="20sp"
53 android:textColor="@color/colorAccent"
54 android:padding="10dp"
55 android:id="@+id/tv_four"
56 app:layout_constraintTop_toBottomOf="@id/tv_two"
57 android:text="@{ageLess20}"
58 android:visibility="@{age < 20 ? view.VISIBLE : view.GONE}"/>
59
60 </androidx.constraintlayout.widget.ConstraintLayout>
61</layout>
上面的例子中,我们看到了几个新鲜的符号,如:
-
userBean.firstName ?? userBean.lastName
:表示firstName
不为空时选择firstName
,为空时则选择lastName
; -
userBean.lastName.isEmpty() ? "lastName is null" : userBean.lastName
:这个就是完完整整的一个三元操作符,相信大家在日常开发中都已经熟练使用过了; -
age > 20 ? view.VISIBLE : view.GONE
:这是判断的表达式,age
大于20的时候View
显示,否则隐藏; -
age < 20 ? view.VISIBLE : view.GONE
:咋一看,感觉好像没见过<
,其实它是就是<
符号,不过要求强制写成这样罢了。
除了上面例举的几个以外,还提供了许多操作符,下面一一列出来:
-
数学表达式:
+ - / * %
-
字符串表达式:
+
-
逻辑表达式:
&& ||
-
二进制表达式:
& | ^
-
一元表达式:
+ - ! ~
-
位移表达式:
>> >>> <<
-
比较表达式:
== > < >= <=
(其中<
需要写成<
) -
instanceof
-
()
-
文字类型:character, String, numeric,
null
-
强转:
Cast
-
方法回调
-
属性引用
-
数组
-
三元表达式:
? :
具体的大家可以去 Android 官网上面去看,上面还有详细的案例。(狗头提醒:需要借助 tizi)
DataBinding 绑定方法
方法的绑定分为两种,一种是带参数的方法,另外一种是不带参数的方法。其实处理起来都是很类似的,方法带参数的话,我们可以通过 variable
来导入参数,然后在 Activity
中赋值就行了。在看下面的例子之前,我们首先要知道,绑定方法的形式都是以 lambda
来实现的。
先看 XML
文件:
1<?xml version="1.0" encoding="utf-8"?>
2
3<layout>
4
5 <data>
6 <!--方法所属类,方法的定义不一定非要在Activity中,可以在任何类中,只需要再次定义就好-->
7 <variable name="activity" type="com.taonce.myjetpack.data.binding.function.FunctionActivity"/>
8 <!--通过variable把方法的参数传递过来-->
9 <variable name="isToast" type="Boolean"/>
10 </data>
11 <androidx.constraintlayout.widget.ConstraintLayout
12 xmlns:android="http://schemas.android.com/apk/res/android"
13 xmlns:tools="http://schemas.android.com/tools"
14 xmlns:app="http://schemas.android.com/apk/res-auto"
15 android:layout_width="match_parent"
16 android:layout_height="match_parent"
17 tools:context=".data.binding.function.FunctionActivity">
18
19 <Button android:layout_width="match_parent"
20 android:layout_height="wrap_content"
21 android:id="@+id/bt_one"
22 app:layout_constraintTop_toTopOf="parent"
23 android:text="不带参数的方法"
24 android:textAllCaps="false"
25 android:onClick="@{() -> activity.buttonClick()}"/>
26
27 <Button android:layout_width="match_parent"
28 android:layout_height="wrap_content"
29 android:id="@+id/bt_two"
30 app:layout_constraintTop_toBottomOf="@id/bt_one"
31 android:text="带参数的方法"
32 android:textAllCaps="false"
33 android:onClick="@{() -> activity.toast(isToast)}"/>
34
35 </androidx.constraintlayout.widget.ConstraintLayout>
36</layout>
再来看看具体的方法:
1class FunctionActivity : AppCompatActivity() {
2
3 override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 val binding = DataBindingUtil.setContentView<ActivityFunctionBinding>(
6 this, R.layout.activity_function
7 )
8 // 绑定xml中两个变量
9 binding.activity = this
10 binding.isToast = true
11 }
12
13 /*
14 * 不带参数的方法
15 * */
16 fun buttonClick() {
17 Toast.makeText(this, "button click", Toast.LENGTH_SHORT).show()
18 }
19
20 /*
21 * 带参数的方法
22 * */
23 fun toast(isToast: Boolean) {
24 if (isToast) {
25 Toast.makeText(this, "isToast is true", Toast.LENGTH_SHORT).show()
26 }
27 }
28}
结合可观察字段和可观察对象
可观察字段或可观察对象的意思就是,当这些字段或对象的值发生改变时,与之相关联的 UI 将会自动更新。Android 提供了以下几种可观察类:
-
ObservableBoolean
:布尔类型 -
ObservableByte
:字节类型 -
ObservableChar
:字符类型 -
ObservableShort
:短整型 -
ObservableInt
:整型 -
ObservableLong
:长整型 -
ObservableFloat
:单精度浮点型 -
ObservableDouble
:双精度浮点型 -
ObservableParcelable
:序列化类型
有人说,为什么没有 String
类型的,不存在的,String
虽然不是基本数据类型,但是还是得提供的,毕竟它的上场率可是登顶的,要想使用 String
需要借助 ObservableField
对象,一会来看下如何使用。说了这么多类型不可能一一举例了,在这就以 String
和 Int
为例说明下如何使用这些可观察字段吧。
XML
:
1<?xml version="1.0" encoding="utf-8"?>
2
3<layout>
4 <data>
5 <import type="androidx.databinding.ObservableField"/>
6 <!--因为<需要转义成<-->
7 <variable name="title" type="ObservableField<String>"/>
8 <variable name="activity" type="com.taonce.myjetpack.data.binding.observable.ObservableActivity"/>
9
10 </data>
11 <androidx.constraintlayout.widget.ConstraintLayout
12 xmlns:android="http://schemas.android.com/apk/res/android"
13 xmlns:tools="http://schemas.android.com/tools"
14 xmlns:app="http://schemas.android.com/apk/res-auto"
15 android:layout_width="match_parent"
16 android:layout_height="match_parent"
17 tools:context=".data.binding.observable.ObservableActivity">
18
19 <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
20 app:layout_constraintTop_toTopOf="parent"
21 android:id="@+id/tv_title"
22 android:text="@{title ?? title}"
23 android:textSize="20sp"
24 android:padding="10dp"
25 android:gravity="center"/>
26
27 <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
28 android:id="@+id/bt_observable"
29 android:onClick="@{()->activity.changeContent()}"
30 app:layout_constraintTop_toTopOf="parent"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintLeft_toLeftOf="parent"
33 app:layout_constraintRight_toRightOf="parent"
34 android:text="修改界面的值"
35 android:textSize="20sp"
36 android:padding="10dp"/>
37
38 </androidx.constraintlayout.widget.ConstraintLayout>
39</layout>
Activity
:
1class ObservableActivity : AppCompatActivity() {
2 lateinit var binding: ActivityObservableBinding
3 private val title = ObservableField("ObservableActivity")
4
5 override fun onCreate(savedInstanceState: Bundle?) {
6 super.onCreate(savedInstanceState)
7 binding = DataBindingUtil.setContentView(this, R.layout.activity_observable)
8 binding.activity = this
9 binding.title = title
10 }
11
12 fun changeContent() {
13 // 可观察字段通过set()方法更新值
14 title.set("ChangeTitle")
15 }
16}
在 XML
文件中绑定可观察字段后,在 Activity
通过该字段的 set( value )
方法来更新值并且通知界面更新 UI。
到这里有人会提出疑问,假如我绑定的不是上面的字段,而且某个数据类咋办,难道还是要通过手动去更新 UI ?不不不,官网也提供了方法可将数据类的变量转化为可观察对象,只要值改变,也会自动的更新 UI,一起看看吧。
1class ObservableBean : BaseObservable() {
2
3 // 必须使用 @get:Bindable 注解
4 @get:Bindable
5 var name: String = ""
6 set(value) {
7 field = value
8 // 每当值set()后,通过notifyPropertyChanged()方法去指定更新
9 // 可更新某个值,可以更新整个数据,取决于你BR后面的属性
10 // BR.name 就代表只更新name相关的UI
11 // BR._all 可更新所有的BR中字段相关联的UI
12 notifyPropertyChanged(BR.name)
13 }
14
15 @get:Bindable
16 var age: Int = -1
17 set(value) {
18 field = value
19 notifyPropertyChanged(BR.age)
20 }
21}
代码中注释解释了一部分,这里再加上两点:
-
在 Kotlin 中使用注解的话,必须在工程的
build.gradle
中加上apply plugin: 'kotlin-kapt'
-
每当在类中声明了一个加了注解的变量,都需要
make
一下工程,这样才会在BR
类中生成对应的变量标志,不然系统找不到对应的变量
创建完成之后,就可以像之前绑定数据类一样操作了,跟之前的操作毫无区别,这里就不再过多解释了。
推荐阅读:
如果本文章你发现有不正确或者不足之处,欢迎你在下方留言或者扫描下方的二维码留言也可!

原文始发于微信公众号(Taonce):Android Jetpack成员之一:DataBinding