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