一、概述
DataBinding可以通过在XML中定义一些逻辑,来处理一部分数据与UI的交互,来达到进一步精简视图中交互逻辑的目的。
DataBinding处于一种特别尴尬的地位,findViewById的功能已经被官方推荐用ViewBinding取代,XML已经慢慢开始被官方推出的 新组件Compose取代,DataBinding渐渐已经没有了用武之地,但是现在很多框架还是在用。
二、配置
1、gradle配置
在模块的build.gradle文件中添加如下配置
plugins {
...
id 'kotlin-kapt' //插件,用到注解时添加
}
android {
...
buildFeatures {
dataBinding true //启用dataBinding
}
}
2、xml配置
先看xml的基础样式
<?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">
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3、适配JSK11
如果运行发现报错A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution,可能需要适配jdk11,在镜像站下载jdk11,并在Android Studio中配置:
三、标签说明
1、<layout>标签
<layout>标签是必须的,只有被该标签包裹的布局才能被Databinding识别,所以<layout>是xml文件的根标签。该标签下有且只能拥有二个子标签,一个是<data>标签,用来包裹具体与xml中控件交互的数据或者事件;另一个标签就是布局的实际内容。
2、<data>标签
<data>标签用于声明用到的变量以及变量类型。创建一个简单的UserInfo类:
data class UserInfo(var name: String?, var age: Int) {
override fun toString(): String {
return "UserInfo(name='$name', age=$age)"
}
}
在data标签里声明要使用到的变量名和类的全路径,如下:
<data>
<variable
name="UserInfo"
type="com.abc.testlifecycle.bean.UserInfo" />
</data>
如果多个不同UserInfo类要在XML中被引用,那么可以用import标签导入来简化写法,如下:
<data>
<import type="com.abc.testlifecycle.bean.UserInfo"/>
<variable
name="UserInfo"
type="UserInfo" />
<variable
name="UserInfo2"
type="UserInfo" />
</data>
如果存在import的类名相同的情况,可以使用alias指定别名:
<data>
<import type="com.abc.testlifecycle.bean.UserInfo"/>
<import
alias="ReqUserInfo"
type="com.abc.testlifecycle.req.UserInfo"/>
<variable
name="UserInfo"
type="UserInfo" />
<variable
name="ReqUserInfo"
type="ReqUserInfo" />
</data>
每个<data>标签都会为XML文件生成一个绑定类,名称是根据布局文件名生成首字母大写以驼峰命名法来命名的文件,例如activity_main.xml文件生成的绑定类为ActivityMainBinding。如果想修改默认生成的绑定类名,可以在<data>标签中自定义,如下:
<data class="MainActivityBinding">
...
</data>
四、绑定视图
对于普通的布局xml视图,我们可以通过View.inflate()、LayoutInflater.from(this).inflate()等方法将xml转化为可以访问View的绑定操作类,DataBinding就给我们提供了这么一套方法,由DataBindingUtil提供,inflate原理和以前是一样的:
//ActivityMainBinding为自动生成的绑定类
val binding = DataBindingUtil.inflate<ActivityMainBinding>(
layoutInflater,R.layout.activity_main,parent,true
)
如果是给Activity设置视图,可以这样写:
override fun onCreate(savedInstanceState:Bundle{
super.onCreate(savedInstanceState)
//ActivityMainBinding为自动生成的绑定类
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
}
如果是给Fragment设置视图,可以这样写:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
var binding = DataBindingUtil.inflate<FragmentCustomBinding>(inflater,
R.layout.fragment_custom,
container,
false)
return binding.root
}
}
还有一种简便的方法就是通过绑定类的静态方法进行inflate:
val binding = ActivityMainBinding.inflate(layoutInflater,parent ,true)
除上述方法外,DataBindingUtil类还有一个bind方法,该方法可以传入一个View得到对应的绑定类:
var binding = DataBindingUtil.bind<FragmentCustomBinding>(view)
五、布局文件中的一些常用用法
1、引入对象的数据
<?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>
<variable
name="userInfo"
type="com.abc.testlifecycle.bean.UserInfo" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView //字符串拼接加设置默认值,userInfo为上面data中的对象userInfo
android:id="@+id/tv_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`姓名:`+userInfo.name,default=`姓名:张三`}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在MainActivity中设置值
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.userInfo = UserInfo("李四", 20, "134 5677 7788")
}
2、字符串转换和导包引入更多自定义代码
如果我们想将一个int值设置给TextView应该如何处理?
android:text="@{String.valueOf(userInfo.age),default=`18`}"
里面可以写代码,但是需要导入我们需要的类,比如定义了一个校验手机号的伪代码:
object CheckUtils {
fun checkPhone(phone:String):String = phone
}
data标签内导入
<import type="com.abc.testlifecycle.utils.CheckUtils" />
使用
android:text="@{CheckUtils.INSTANCE.checkPhone(userInfo.phone),default=`123 1245 1234`}"
3、三目运算符
android:text="@{userInfo.age>18?`成年`:`未成年`,default=`成年`}"
空合并运算符 ?? 会取第一个不为 null 的值作为返回值,比如在界面上设置用户名时如果用户名为空,就设置用户手机号为用户名:
android:text="@{userInfo.name??userInfo.phone,default=`未设置用户名`}"
你甚至可以继续追加
android:text="@{userInfo.name??userInfo.phone??`未设置用户名`,default=`用户名`}"
binding.userInfo = UserInfo(null, 20,null)
结果为
4、资源引用和属性控制
引用字符串
<string name="content">%1$s非%1$s,%2$s非%2$s</string>
android:text="@{@string/content(`花`,`雾`)}"
结果
控制控件的可见不可见
<import type="android.view.View" />
android:visibility="@{userInfo.isVip?View.VISIBLE:View.GONE}"
5、加载图片
DataBinding 提供了 @BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。
定义加载图片的方法
object ImageLoaderUtils {
//定义自定义属性的名称
@BindingAdapter("image_url")
//方法静态
@JvmStatic
fun loadImage(image: ImageView, url: String) {
//简单的Glide加载图片的方法
Glide.with(image.context).load(url).into(image)
}
}
使用自定义属性
app:image_url="@{userInfo.photo}"
调用
var url = "https://img.wxcha.com/m00/7c/be/9ddde1a370bca73daa50a01aeabdfb10.jpg"
binding.userInfo = UserInfo(null, 20, null, true, url)
结果
@BindingAdapter可以重新定义或扩展Android控件的属性,比如我们在给TextView设置int值时会报错,可以扩展使它不报错:
//android:text是一个int值
android:text="@{userInfo.age}"
扩展
@BindingAdapter("android:text") //扩展或者重新定义android:text
@JvmStatic
fun setText(textview: TextView, value: Int) {
textview.text = "$value" //int转化为字符串
}
6、Array、List、Set、Map
dataBinding 也支持在布局文件中使用数组、Lsit、Set 和 Map,且在布局文件中都可以通过 list[index] 的形式来获取元素。
而为了和 variable 标签的尖括号区分开,在声明 Lsit< String > 之类的数据类型时,需要使用尖括号的转义字符<为<,>为>。Map类型可以这样写
@{map[key]} 可替换为 @{map.key}
<data>
<import type="java.util.List" />
<import type="com.abc.testlifecycle.bean.UserInfo" />
<variable
name="userInfoList"
type="List<UserInfo>" />
</data>
android:text="@{userInfoList[0].name}"
7、引用同一布局的控件
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
六、可观察字段
ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoubleObservableParcelable可观察字段是具有单个字段的自包含可观察对象。原语版本避免在访问操作期间封箱和开箱。如需使用此机制,请采用 Java 编程语言创建public final属性,或在 Kotlin 中创建只读属性,如以下示例所示:
class User {
val firstName = ObservableField<String>()
val lastName = ObservableField<String>()
val age = ObservableInt()
}
七、可观察集合
某些应用使用动态结构来保存数据。可观察集合允许使用键访问这些结构。以List可观察集合有ObservableList(接口),ArrayList(实现类),ObservableArrayList(实现类),ObservableMap(接口),ArrayMap(实现类),ObservableArrayMap(实现类)等。
<data>
<import type="androidx.databinding.ObservableList" />
<import type="com.abc.testlifecycle.bean.UserInfo" />
<variable
name="userInfoList"
type="ObservableList<UserInfo>" />
</data>
八、可观察对象
实现 Observable 接口的类允许注册监听器,以便它们接收有关可观察对象的属性更改的通知。
Observable 接口具有添加和移除监听器的机制,但何时发送通知必须由您决定。为便于开发,数据绑定库提供了用于实现监听器注册机制的 BaseObservable类。实现 BaseObservable 的数据类负责在属性更改时发出通知。具体操作过程是向 getter 分配 Bindable注释,然后在 setter 中调用 notifyPropertyChanged() 方法,如以下示例所示:
class User : BaseObservable() {
@get:Bindable
var firstName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.firstName)
}
@get:Bindable
var lastName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.lastName)
}
}
九、LiveData和ViewModel关联使用
要将 LiveData 对象与绑定类一起使用,您需要指定生命周期所有者来定义 LiveData 对象的范围。以下示例在绑定类实例化后将 Activity 指定为生命周期所有者:
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
}
}
使用 ViewModel组件来将数据绑定到布局。在 ViewModel 组件中,您可以使用 LiveData 对象转换数据或合并多个数据源。以下示例展示了如何在 ViewModel 中转换数据:
class ScheduleViewModel : ViewModel() {
val userName: LiveData
init {
val result = Repository.userName
userName = Transformations.map(result) { result -> result.value }
}
}
要将 ViewModel 组件与数据绑定库一起使用,必须实例化从 ViewModel 类继承而来的组件,获取绑定类的实例,并将您的 ViewModel 组件分配给绑定类中的属性。以下示例展示了如何将组件与库结合使用:
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Obtain the ViewModel component.
val userModel: UserModel by viewModels()
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)
// Assign the component to a property in the binding class.
binding.viewmodel = userModel
}
}
在您的布局中,使用绑定表达式将 ViewModel 组件的属性和方法分配给对应的视图,如以下示例所示:
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />
十、双向绑定
@={} 表示法(其中重要的是包含“=”符号)可接收属性的数据更改并同时监听用户更新。
一个简单的双向绑定的例子:
<data>
<import type="com.abc.testlifecycle.bean.UserInfo" />
<variable
name="userInfo"
type="UserInfo" />
</data>
<EditText
android:id="@+id/et_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={userInfo.name}" //将userInfo.name设置给edittext,同时输入文本改变userInfo.name
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
将userInfo.name设置给edittext,同时输入文本改变userInfo.name
var userInfo = UserInfo("zhangsan", 20, null, false, url)
binding.userInfo= userInfo
binding.etStart.addTextChangedListener(object :TextWatcher{
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
Log.d(TAG,"打印name=${userInfo.name}")
}
})
参考了以下博客,表示感谢:
Android Jetpack(2):DataBinding的基本使用