关于kotlin:DataBinding系列之基础使用

54次阅读

共计 9728 个字符,预计需要花费 25 分钟才能阅读完成。

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, 这样👆的代码就不会编译出错了。

@BindingConversion
fun 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 开源之旅】

正文完
 0