Jetpack学习笔记之DataBinding (6)
是什么?
官方定义:数据绑定库(DataBinding)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
和我们上节介绍的视图绑定viewBinding类似,二者都会生成可用于直接引用视图的绑定类。 但是,dataBinding功能更加强大。
可以将布局页面中的组件与应用中的数据绑定,同时支持单向绑定与双向绑定,即:
指数据的变化会驱动页面的变化,同时也支持页面的变化驱动数据的变化。 即解决MVVM架构中数据和视图之间的绑定。
DataBinding 涉及的知识点比较多,比如:
- 可以在布局文件中引入对象,绑定布局变量与数据模型中的成员变量
- 绑定表达式中支持常用运算符和关键字、属性应用、其它视图
- 可以引用资源、处理事件等
- 更重要的是,可以绑定到架构组件,和ViewModel 和LiveData结合。
DataBinding的优势:
- 项目更简洁,可读性更高。部分与UI控件相关的工作可以在布局文件中完成。
- 不再需要findViewById()方法。
- 布局文件可以包含简单的业务逻辑。
- View可以直接与数据模型中的字段绑定,甚至能够响应用户的交互。
- 双向绑定
- 释放Activity/Fragment
- 数据绑定空安全,可以减少NullpointException。
PS:对应DataBinding,个人认为使用时还是应该有所取舍的,因为在XML中掺杂UI逻辑,看似释放了Activity/Fragment了,但是可读性和维护性可能会是个问题。
如何使用?
介绍了那么多,还是先看一个简单的示例:
编译环境
DataBinding兼容性较好,Android 4.0以上都支持。
新建一个EmptyActivity项目,在应用模块的 build.gradle 文件中添加 dataBinding 元素,如以下示例所示:
android {
dataBinding {
enabled = true
}
}
将布局文件转换为data bind 布局
选择布局根view右键显示 Show Context Actions(快捷键是 Alt+回车):
再点击后,Convert to data binding layout 即可:
可以看到布局文件自动变为如下,自动添加layout 标签 和data 标签:
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="Hello World!"
android:textSize="24sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
其中:data元素即用来声明在此布局用使用到的变量以及变量的类型。
创建一个data中需要使用的数据类
data class Student(val name:String,val age:Int)
data 中使用数据
现在,布局 data 中即可以使用此数据对象。
<data>
<variable
name="student"
type="com.itshizhan.databindingdemo.Student" />
</data>
同时,布局文件中即可以使用此数据类的属性了。
布局中的表达式使用“@{}”语法写入特性属性中。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{student.name}"
android:textAlignment="center"
android:textSize="18sp"
android:layout_marginTop="50dp"/>
<TextView
android:id="@+id/tv_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{student.age}"
android:textAlignment="center"
android:textSize="18sp"
android:layout_marginTop="20dp"/>
</LinearLayout>
Activity 中绑定数据
和视图绑定一样,使用dataBinding后,setContentView()就不需要了,要适当修改。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding =
ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
当然,jetpack 中,提供了DataBindingUtil工具类:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView( this,
R.layout.activity_main)
}
}
现在运行,一些正常,但是Student数据了并没有赋值啊。如何绑定数据呢?
我们查看生成的 ActivityMainBinding.java 文件如下:
public abstract class ActivityMainBinding extends ViewDataBinding {
@NonNull
public final TextView tvAge;
@NonNull
public final TextView tvName;
@Bindable
protected Student mStudent;
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView tvAge, TextView tvName) {
super(_bindingComponent, _root, _localFieldCount);
this.tvAge = tvAge;
this.tvName = tvName;
}
public abstract void setStudent(@Nullable Student student);
@Nullable
public Student getStudent() {
return mStudent;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding bind(@NonNull View view) {
return bind(view, DataBindingUtil.getDefaultComponent());
}
}
可以看到,已经生成了tvName、tvAge属性,以及getter和setter方法,那就很再调用下就ok了
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main)
// setStudent(),数据改变视图改变
binding.student = Student("Jack Ma", 18)
}
}
查看效果
我们再运行一下,结果直接crash了,检查一下错误,发现:
我们给student 赋值时,传入的age 是Int类型,即 @{student.age}返回Int类型,而TextView的text属性需要的是字符串类型。
这个简单,之前我们就提到, @{ } 是支持关键字和运算符等的,转换一下即可:
<TextView
android:id="@+id/tv_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(student.age)}"
android:textAlignment="center"
android:textSize="18sp"
android:layout_marginTop="20dp"/>
如下:
和架构组件一起使用
DataBinding的功能很多,也很简单,具体的可以看官方文档:
https://developer.android.google.cn/topic/libraries/data-binding
除了普通的数据源,如果是LiveData将数据变化通知给界面呢?
例如:
class MainLiveDataViewModel : ViewModel() {
private val _name = MutableLiveData("JackMa")
private val _age = MutableLiveData(18)
val name: LiveData<String> = _name
val age: LiveData<Int> = _age
// 省略部分代码
}
只需要以下三步即可完成双向绑定:
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// 1. 获取viewModel实例
val viewModel = ViewModelProvider(this).get(MainLiveDataViewModel::class.java)
// 2. 绑定ViewModel
binding.viewmodel = viewModel
// 3. 指定生命周期所有者来定义 LiveData 对象的范围
binding.lifecycleOwner = this
}
}
使用 Observable ViewModel
在某些情况下,您可能更愿意使用实现 Observable 接口的 ViewModel 组件,而不是使用 LiveData 对象,即使这样会失去对 LiveData 的生命周期管理功能也不影响。使用实现 Observable的 ViewModel 组件可让您更好地控制应用中的绑定适配器。例如,这种模式可让您更好地控制数据更改时发出的通知,您还可以指定自定义方法来设置双向数据绑定中的属性值。
使用方法可以查看示例代码,我已经上传到gitee上了,不在具体展示了。