本文讲解 Data Binding 基本用法,单向绑定,双向绑定。
官方文档:https://developer.android.google.cn/topic/libraries/data-binding
一句话介绍 Data Binding :Data Binding 库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
注:本文使用 Kotlin 编写。
在应用模块的 build.gradle
文件中添加 dataBinding
元素:
android {
...
dataBinding {
enabled = true
}
}
目录
一、基本用法
二、单向绑定
三、双向绑定
一、基本用法
1. 创建一个 XML 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
2. 在父布局最左侧使用键盘快捷键 alt + enter ,就会出现提示,选择 "Convert to data binding layout" 选项
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3. 新建一个 User 类
data class User(var name: String, var age: Int)
4. 在布局中绑定 User 类,这样就把 User 对象引入到布局中。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user_bean"
type="com.tyhoo.jetpack.databinding.basic.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
5. 完善布局
<?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="user_bean"
type="com.tyhoo.jetpack.databinding.basic.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Name"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guide_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Age"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guide_vertical"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
6. 编写 Activity ,绑定布局
class BasicActivity : AppCompatActivity() {
lateinit var mBinding: ActivityBasicBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic)
}
}
7. 完善 Activity ,将 User 绑定到布局上
class BasicActivity : AppCompatActivity() {
lateinit var mBinding: ActivityBasicBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic)
var user = User("Android", 10)
mBinding.userBean = user
}
}
8. 在布局上去操作这个绑定
<?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">
...
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<TextView
...
android:text="@{user_bean.name}"
... />
<TextView
...
android:text="@{String.valueOf(user_bean.age)}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
9. 运行程序,发现不需要我们对布局进行赋值,User 对应的值就已经绑定到两个 TextView 上。
10. ActivityBasicBinding 是 Data Binding 根据布局文件 activity_basic 帮我们自动生成的,所以对这个类名进行自定义
<?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 class="BasicBinding">
...
</data>
...
</layout>
11. 回到 BasicActivity ,发现 ActivityBasicBinding 这个类名已经不存在了,使用刚才定义的新类名进行替换
class BasicActivity : AppCompatActivity() {
lateinit var mBinding: BasicBinding
...
}
12. 在布局文件 TextView 的表达式中,可以指定一个 Default ,方便在编写的过程中进行界面预览。
<?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">
...
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<TextView
...
android:text="@{user_bean.name,default = Name}"
... />
<TextView
...
android:text="@{String.valueOf(user_bean.age),default=Age}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
二、单向绑定
在 Activity 中,数据发生变化,会自动显示在当前界面上。
比如说:User 的 name 或 age 变了,设置 User 属性的同时,界面会跟着改变。
1. 继承于 DataBinding 中的 Observable 类
class User : BaseObservable() {}
2. 把 User 里面的 name 和 age 写到 {} 内,因为继承 BaseObservable 我们要重写它的 get 和 set 方法,所以在 Kotlin 中 data 模式就不能在使用了,因为它的 get 和 set 是默认实现。
class User : BaseObservable() {
var name: String = ""
var age: Int = 0
}
3. 重写 set ,有三种:
- set 默认实现
- set(value) = ... 相当于等于一个函数值,在 Kotlin 中它也是一个对象
- set(value) = {...} 相当于重写 set 方法,但是这个 set 方法一定要紧跟当前这个成员变量的下面
我们使用第三种:
class User : BaseObservable() {
var name: String = ""
set(value) {
}
var age: Int = 0
set(value) {
}
}
4. 使用 Data Binding 注解来完善 User
前提:在应用模块的 build.gradle
文件中引用插件 apply plugin: 'kotlin-kapt'
class User : BaseObservable() {
@Bindable
var name: String = ""
set(value) {
// 给一个默认实现,相当于 Java 中的 this.name = name
field = value
// 因为继承了 BaseObservable ,所以要观察 name 值的变化
notifyPropertyChanged(BR.name)
}
@Bindable
var age: Int = 0
set(value) {
// 给一个默认实现,相当于 Java 中的 this.age = age
field = value
// 因为继承了 BaseObservable ,所以要观察 age 值的变化
notifyPropertyChanged(BR.age)
}
}
notifyChange() 和 notifyPropertyChanged() 区别:
notifyPropertyChanged 存了一个 String 值,相当于它引用了 name 。通知界面 name 这个变量发生改变。
notifyChange 相当于它会通知整个 User 对象进行更新,是全局的,更耗性能。
5. 修改布局文件,追加 Button 按钮
<?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">
...
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<Button
android:id="@+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guide_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />
<Button
android:id="@+id/btn_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guide_vertical"
app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
6. 在 Activity 添加点击事件进行动态更新
class OneWayActivity : AppCompatActivity() {
lateinit var mBinding: OneWayBinding
lateinit var mUser: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_one_way)
mUser = User()
mBinding.userBean = mUser
btn_1.setOnClickListener {
mUser.name = "AAA"
mUser.age = 20
}
btn_2.setOnClickListener {
mUser.name = "BBB"
mUser.age = 30
}
}
}
7. 运行代码,点击 Button 的时候,name 和 age 都会动态更新。
8. 一个问题
Q:如果项目特别大,需要在每个成员变量下面写 notifyPropertyChanged 或 notifyChange 吗?
A:Google 在 Observable 基础之上有封装了一个包装类,叫 ObservableField 。这个类默认实现了 notify 方法。所以也就不需要去写 set 和 get 。
修改 User 代码:
class User : BaseObservable() {
@Bindable
var name: ObservableField<String> = ObservableField("")
@Bindable
var age: ObservableField<Int> = ObservableField(0)
}
修改 Activity 代码:
class OneWayActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
btn_1.setOnClickListener {
mUser.name.set("AAA")
mUser.age.set(20)
}
btn_2.setOnClickListener {
mUser.name.set("BBB")
mUser.age.set(30)
}
}
}
题外话:
这样写 User 会减少很多代码,但是会有一个问题,如果 User 类从各种地方(如:本地、网络等)序列化的,它不会直接序列化成 ObservableField 类,看一下 ObservableField 源码:
public class ObservableField<T> extends BaseObservableField implements Serializable {}
虽然它实现了 Serializable ,但是序列化过程中的损耗不理想,一层层的封装,序列化过程中会出现一些问题,所以 Data Binding 有利也有弊,在实际项目中有局限性。
三、双向绑定
MVVM 架构模式的精髓,View 改变,数据同时也跟着改变。
Data Binding 最让人惊艳的地方就是它的双向绑定。数据驱动界面,反过来用界面的事件去驱动数据。
1. 数据绑定
1.1 在布局里添加一个 EditText ,将它和 name 进行绑定
<?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">
...
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="@string/app_name"
android:text="@={user_bean.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
此时 User 的 name 和 EditText 的内容会动态的改变。
1.2 运行代码看效果:
点击 Button ,TextView 会变成 AAA ,然后 EditText 会动态的变成 AAA。 说明界面是根据数据的改变进行改变的。
1.3 在 EditText 上输入内容:
此时数据会根据 EditText 界面上的内容进行改变,然后将数据传递到各处。
2. 事件绑定
在 View 的界面上绑定一个事件,界面的事件会传到 Activity 事件的方式方法上。
例如:
2.1 把 Button 事件提到一个单独的类里:
class Listener(val user: User) {
fun changeAge() {
user.age.set(user.age.get() ? .plus(1))
}
}
2.2 在界面中导入这个 Listener
<?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 class="TwoWayBinding">
...
<variable
name="listener"
type="com.tyhoo.jetpack.databinding.twoway.TwoWayActivity.Listener" />
</data>
...
</layout>
2.3 对界面的 Button 做点击事件
<?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">
...
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<Button
android:id="@+id/btn_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> listener.changeAge()}"
android:text="Age"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guide_vertical"
app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2.4 在 Activity 里设置这个 Listener
class TwoWayActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mBinding.listener = Listener(mUser)
...
}
class Listener(val user: User) {
fun changeAge() {
user.age.set(user.age.get()?.plus(1))
}
}
}
2.5 运行代码,点击 Button ,可以动态的改变界面数据。
如果本文对你有帮助,请点赞支持!!!