1.前言

DataBinding, 又名数据绑定,是Android开发中十分重要的根底技术,它能够将UI组件和数据模型连接起来,使得在数据模型发生变化时,UI组件自动更新,从而节俭了大量的代码和工夫。

DataBinding的原理是通过编写XML布局文件,在其中应用特定的标签和语法,将UI组件和数据模型连接起来。当布局文件被加载时,DataBinding会主动生成绑定代码,从而将UI组件和数据模型关联起来。

通过学习DataBinding基础知识,能够让你的代码速度翻倍,进步开发效率和代码品质。因而,如果你心愿在Android开发中取得更高的成功率和更快的倒退速度,那么请务必学习DataBinding技术,把握其基础知识,让本人成为一名高效率的Android开发者!

那么话不多说,让咱们间接直奔主题。接下来我将从实用性的角度,来逐个解说DataBinding的根底应用,文章开端会给出示例代码的链接地址,心愿能给你带来启发。


2.筹备工作

2.1 启用

1.DataBinding启用

android {    dataBinding {        enabled = true    }}

2.ViewBinding启用

android {    buildFeatures {        viewBinding true    } }

2.2 快捷方式

在你的布局中找到最外层的布局,将光标放在如图地位。

  • Windows 请按快捷键 Alt + 回车
  • Mac 请按快捷键 option + 回车


3.DataBinding绑定

3.1 数据类型

通常咱们在DataBinding中绑定的数据类型是ViewModel或者是AndroidViewModel,它俩都是生命周期可感知的,惟一的区别是AndroidViewModel能够获取到利用的上下文Application

3.2 数据创立

ViewModel的创立通常是通过ViewModelProvider进行创立和获取。

ViewModelProvider(this).get(Xxx::class.java)

而在ViewModel中,通常应用MutableLiveData作为可变UI响应数据类型。相比拟LiveData而言,它凋谢了批改值的接口,上面是一个ViewModel的简略例子:

class RecyclerViewRefreshState(application: Application) : AndroidViewModel(application) {    val title = MutableLiveData("RecyclerView的刷新和加载更多演示")    val isLoading = MutableLiveData(false)    val sampleData = MutableLiveData<List<SimpleItem>>(arrayListOf())    val loadState = MutableLiveData(LoadState.DEFAULT)    val layoutStatus = MutableLiveData(Status.DEFAULT)}

当然了,如果你有一个LiveData会随着一个或多个LiveData的变动而变动,这个时候你可能就须要应用MediatorLiveData,即合并LiveData。

这里我简略利用MediatorLiveData实现一个组合的LiveData--CombinedLiveData

open class CombinedLiveData<T>(vararg liveData: LiveData<*>, block: () -> T) :    MediatorLiveData<T>() {    init {        value = block()        liveData.forEach {            addSource(it) {                val newValue = block()                if (value != newValue) {                    value = newValue                }            }        }    }}fun <R, T1, T2> combineLiveData(    liveData1: LiveData<T1>,    liveData2: LiveData<T2>,    block: (T1?, T2?) -> R) = CombinedLiveData(liveData1, liveData2) { block(liveData1.value, liveData2.value) }

这个时候,咱们就能够通过combineLiveData办法将两个LiveData组合起来,造成一个新的LiveData。上面我简略给出一个示例代码:

class CombineLiveDataState : DataBindingState() {    val userName = MutableLiveData("小明")    val userAge = MutableLiveData(20)    val userInfo = combineLiveData(userName, userAge) { name, age ->        "${name}往年${age}岁了!"    }    fun onAgeChanged() {        userAge.value = userAge.value?.plus(1)    }}

这里变动了userAge的值后,userInfo也会随着一起变动。

3.3 视图绑定

个别咱们应用DataBindingUtil进行视图绑定操作。绑定操作咱们可分为:绑定Activity、绑定Fragment和绑定View。

  1. 绑定Activity

应用DataBindingUtil.setContentView办法进行绑定。

fun <DataBinding : ViewDataBinding> bindActivity(    activity: ComponentActivity,    layoutId: Int): DataBinding = DataBindingUtil.setContentView<DataBinding>(activity, layoutId).apply {    lifecycleOwner = activity}
  1. 绑定Fragment

应用DataBindingUtil.inflate办法进行绑定。

fun <DataBinding : ViewDataBinding> bindFragment(    fragment: Fragment,    inflater: LayoutInflater,    layoutId: Int,    parent: ViewGroup? = null,    attachToParent: Boolean = false): DataBinding = DataBindingUtil.inflate<DataBinding>(inflater, layoutId, parent, attachToParent).apply {    lifecycleOwner = fragment.viewLifecycleOwner}
  1. 绑定View

应用DataBindingUtil.bind办法进行绑定。

fun <DataBinding : ViewDataBinding> bindView(    view: View,    viewLifecycleOwner: LifecycleOwner,): DataBinding = DataBindingUtil.bind<DataBinding>(view).apply {    lifecycleOwner = viewLifecycleOwner}

【⚠️特地注意事项⚠️️】

DataBinding绑定的时候,肯定要给ViewDataBinding赋值LifecycleOwner, 否则ViewModel中的LiveData产生数据扭转后,则不会告诉UI组件进行页面更新。

3.4 数据绑定

对ViewModel的绑定有两种写法。

  • 间接应用ViewDataBinding.variableId = xxx间接赋值。
val mainState = ViewModelProvider(this).get(MainState::class.java)activityMainbinding.state = mainState
  • 应用ViewDataBinding.setVariable(int variableId, @Nullable Object value)进行赋值。
val mainState = ViewModelProvider(this).get(MainState::class.java)binding.setVariable(BR.state, mainState)

这两者的惟一区别在于,第一种须要晓得ViewDataBinding的具体类型,而第二种是ViewDataBinding本身的办法,无需晓得ViewDataBinding的具体类型。

一般来说在框架中应用到泛型未知ViewDataBinding具体类型的时候,都会应用第二种形式进行绑定,能够说第二种形式更通用一些。


4.根底应用

4.1 点击事件绑定

1.无参响应函数:

fun onIncrement() {    // 办法体}
android:onClick="@{() -> state.onIncrement()}"

2.接口变量响应函数

留神,这里变量的类型应该是View.OnClickListener接口。

val onClickDecrement = View.OnClickListener {    // 办法体}
android:onClick="@{state.onClickDecrement}"

3.有参响应函数

fun onReset(view: View) {    // 办法体}
// 第一种写法android:onClick="@{(view) -> state.onReset(view)}" // 第二种写法android:onClick="@{state::onReset}"

4.2 @BindingAdapter自定义属性

所有注解的性能都是基于XML属性值为DataBinding表达式才失效(即@{})

应用@BindingAdapter进行控件自定义属性绑定的时候,肯定要应用 "@{}" 进行赋值,这一点十分重要!!!

  1. 顶级函数实现
// Kotlin拓展函数式写法, 举荐应用@BindingAdapter("customTitle")fun TextView.setCustomTitle(title: String) {    text = "题目1: $title"}// 第一个参数必须是view的子类@BindingAdapter("customTitle1")fun setCustomTitle1(view: TextView, title: String) {    view.text = "题目2: $title"}// 多个参数进行绑定,requireAll=true,代表两个参数都设置了才失效,默认是true.// 如果requireAll为false, 你没有填写的属性值将为null. 所以须要做非空判断.@BindingAdapter(value = ["customTitle", "customSize"], requireAll = true)fun TextView.setTextContent(title: String, size: Int) {    text = "题目3: $title"    textSize = size.toFloat()}

【⚠️特地注意事项⚠️️】

很多时候,很多老手在写DataBinding的时候,常常会漏掉"@{}",尤其是用数字和Boolean类型的值时。就比方我下面设置的customSize属性,类型值是Int型,正确的写法应该是上面这样:

  • 正确的写法
<TextView    style="@style/TextStyle.Title"    android:layout_marginTop="16dp"    app:customSize="@{25}"    app:customTitle="@{state.title}" />
  • 常见谬误的写法
<TextView    style="@style/TextStyle.Title"    android:layout_marginTop="16dp"    app:customSize="25"    app:customTitle="@{state.title}" />

上述谬误的写法,运行后编译器会报错AAPT: error: attribute customSize (aka com.xuexiang.databindingsample:customSize) not found.

所以当咱们写DataBinding的时候,如果呈现AAPT: error: attribute xxx (aka com.aa.bb:xxx) not found.,十有八九是你赋值漏掉了"@{}"

  1. 单例类+@JvmStatic注解
object TitleAdapter {    @JvmStatic    @BindingAdapter("customTitle2")    fun setCustomTitle2(view: TextView, title: String) {        view.text = "题目4: $title"    }}

4.3 @BindingConversion自定义类型转换

作用:在应用DataBinding的时候,对属性值进行转换,以匹配对应的属性。
定义:办法必须为公共动态(public static)办法,且有且只能有1个参数。

上面我给一个简略的例子:

1.对于User类,age的类型是Int。

data class User(    val name: String,    val gender: String? = "男",    val age: Int = 10,    val phone: String? = "13124765438",    val address: String? = null)

2.应用@BindingAdapter定义了age的类型却是String。

@BindingAdapter(value = ["name", "age"], requireAll = true)fun TextView.setUserInfo(name: String, age: String) {    text = "${name}往年${age}岁"}

3.这时候应用DataBinding的时候,的app:age="@{state.user.age}"会编译报错,提醒类型不匹配。

<TextView    style="@style/TextStyle.Title"    android:layout_marginTop="16dp"    app:name="@{state.user.name}"    app:age="@{state.user.age}"/>

4.这个时候,咱们就能够应用@BindingConversion自定义类型转换: Int -> String, 这样的代码就不会编译出错了。

@BindingConversionfun int2string(integer: Int) = integer.toString()

4.4 @{}中表达式应用

  1. 罕用运算符
  • 算术 + - / * %
  • 字符串合并 +
  • 逻辑 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比拟 == > < >= <=
  • 三元 ?:
  • Array 拜访 []
<TextView    android:text="@{@string/app_name +  @string/app_name}"/>
<TextView     android:visibility="@{!state.user.phone.empty ? View.VISIBLE : View.GONE}"/>
  1. 罕用转义字符
  • 空格: \&nbsp;
  • <小于号: \&lt;
  • \>大于号: \&gt;
  • &与号: \&amp;
<TextView     android:visibility="@{!state.user.phone.empty &amp;&amp; state.user.age > 5 ? View.VISIBLE : View.GONE}"/>
  1. 资源应用

@string @color @drawable @dimen @array

<TextView    style="@style/TextStyle.Content"    android:text="@{@string/user_format(state.user.name, state.user.gender)}"    android:textColor="@{@color/toast_error_color}"    android:textSize="@{@dimen/xui_config_size_content_text_phone}" />
  1. 汇合

汇合不属于java.lang*下, 须要导入全门路。汇合应用[]进行拜访。

<data>    <import type="java.util.List"/>    <import type="android.util.SparseArray"/>    <import type="java.util.Map"/>    <variable name="list" type="List&lt;String>"/>    <variable name="sparse" type="SparseArray&lt;String>"/>    <variable name="map" type="Map&lt;String, String>"/></data>
<TextView    android:text="@{`key: key1, value:` + map[`key1`]}" />
  1. 援用类的静态方法

kotlin中定义静态方法,肯定要在办法上加上@JvmStatic,否则将无奈胜利援用。

(1) 定义方法

object AppUtils {    @JvmStatic    fun getAppInfo(context: Context?) =        context?.let {            "packageName: ${it.packageName}, \nversionName: ${                it.packageManager.getPackageInfo(                    it.packageName,                    0                ).versionName            }"        }}

(2) 导入办法所在类门路

<import type="com.xuexiang.databindingsample.utils.AppUtils"/>

(3) 援用办法

<TextView    android:text="@{AppUtils.getAppInfo(context)}"/>
  1. 空值合并运算符

空值合并运算符 ?? 会取第一个不为 null 的值作为返回值。

<TextView    android:text="@{`地址:` + (state.user.address ?? `默认地址`)}"/>

等价于

<TextView    android:text="@{state.user.address != null ?  state.user.address : `默认地址`)}"/>

4.5 include 和 ViewStub

在主布局文件中将相应的变量传递给 include 布局,需应用自定义的 bind 命名空间将变量传递给 (include/ViewStub), 从而使两个布局文件之间共享同一个变量。

例如,在include中定义的变量id是:<variable name="user" type="...User"/>, 那么就应用 app:user="@{state.user}" 来绑定数据,与variable定义的name保持一致。

  1. include
<include    android:id="@+id/include_layout"    layout="@layout/include_user_info"    app:user="@{state.user}" />
<layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <variable            name="user"            type="com.xuexiang.databindingsample.fragment.basic.model.User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginVertical="16dp"        android:orientation="vertical">                <TextView            android:id="@+id/tv_title"            style="@style/TextStyle.Content"            android:userInfo="@{user}" />    </LinearLayout></layout>

如果你想在页面中获取include援用布局的某个控件时,你须要给include设置资源id,而后通过它去拜访援用布局中的控件,就以的例子为例,如果我想拜访布局中的TextView,咱们能够这样写:

binding?.includeLayout?.tvTitle?.text = "用户信息"

【⚠️特地注意事项⚠️️】

这里须要留神的是,include标签,如果设置了layout_widthlayout_height这两个属性,那么布局就是由include外层设置的layout属性失效,内层属性不失效。

如果include标签没有设置layout_widthlayout_height这两个属性,那么就是由include援用的布局内层设置的layout属性失效。

举个例子,如果把设置的include改成上面这样:

<include    layout="@layout/include_user_info"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_marginTop="24dp"    app:user="@{state.user}" />

那么@layout/include_user_info加载的布局,间隔上部的间隔就是24dp,而不是16dp。

  1. ViewStub
<ViewStub    android:id="@+id/user_info"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_marginTop="16dp"    android:layout="@layout/viewstub_user_info"    app:info="@{state.user}" />
<layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <variable            name="info"            type="com.xuexiang.databindingsample.fragment.basic.model.User" />    </data>    <TextView        style="@style/TextStyle.Content"        android:userInfo="@{info}" /></layout>

因为ViewStub性能是提早加载援用的布局,当咱们须要让其进行加载的时候,咱们须要通过ViewStub的资源id获取到ViewStub,而后进行inflate,示例代码如下:

binding?.userInfo?.viewStub?.inflate()

最初

以上就是本次DataBinding根底应用的全部内容,前面我还会分享DataBinding的进阶应用教程,感兴趣的小伙伴能够点击头像关注我哦~

本文的全副源码我都放在了github上, 感兴趣的小伙伴能够下下来钻研和学习。

我的项目地址: https://github.com/xuexiangjys/DataBindingSample

我是xuexiangjys,一枚酷爱学习,喜好编程,勤于思考,致力于Android架构钻研以及开源我的项目教训分享的技术up主。获取更多资讯,欢送微信搜寻公众号:【我的Android开源之旅】