DataBinding系列之基础使用

2023-09-01 11:21:21 浏览数 (1)

1.前言

DataBinding, 又名数据绑定,是Android开发中非常重要的基础技术,它可以将UI组件和数据模型连接起来,使得在数据模型发生变化时,UI组件自动更新,从而节省了大量的代码和时间。

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

通过学习DataBinding基础知识,可以让你的代码速度翻倍,提高开发效率和代码质量。因此,如果你希望在Android开发中获得更高的成功率和更快的发展速度,那么请务必学习DataBinding技术,掌握其基础知识,让自己成为一名高效率的Android开发者!

那么话不多说,让我们直接直奔主题。接下来我将从实用性的角度,来逐一讲解DataBinding的基础使用,文章末尾会给出示例代码的链接地址,希望能给你带来启发。


2.准备工作

2.1 启用

1.DataBinding启用

代码语言:javascript复制
android {
    dataBinding {
        enabled = true
    }
}

2.ViewBinding启用

代码语言:javascript复制
android {
    buildFeatures {
        viewBinding true
    } 
}

2.2 快捷方式

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

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

3.DataBinding绑定

3.1 数据类型

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

3.2 数据创建

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

代码语言:javascript复制
ViewModelProvider(this).get(Xxx::class.java)

而在ViewModel中,通常使用MutableLiveData作为可变UI响应数据类型。相比较LiveData而言,它开放了修改值的接口,下面是一个ViewModel的简单例子:

代码语言:javascript复制
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

代码语言:javascript复制
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。下面我简单给出一个示例代码:

代码语言:javascript复制
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方法进行绑定。

代码语言:javascript复制
fun <DataBinding : ViewDataBinding> bindActivity(
    activity: ComponentActivity,
    layoutId: Int
): DataBinding = DataBindingUtil.setContentView<DataBinding>(activity, layoutId).apply {
    lifecycleOwner = activity
}
  1. 绑定Fragment

使用DataBindingUtil.inflate方法进行绑定。

代码语言:javascript复制
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方法进行绑定。

代码语言:javascript复制
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直接赋值。
代码语言:javascript复制
val mainState = ViewModelProvider(this).get(MainState::class.java)
activityMainbinding.state = mainState
  • 使用ViewDataBinding.setVariable(int variableId, @Nullable Object value)进行赋值。
代码语言:javascript复制
val mainState = ViewModelProvider(this).get(MainState::class.java)
binding.setVariable(BR.state, mainState)

这两者的唯一区别在于,第一种需要知道ViewDataBinding的具体类型,而第二种是ViewDataBinding自身的方法,无需知道ViewDataBinding的具体类型。

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


4.基础使用

4.1 点击事件绑定

1.无参响应函数:

代码语言:javascript复制
fun onIncrement() {
    // 方法体
}
代码语言:javascript复制
android:onClick="@{() -> state.onIncrement()}"

2.接口变量响应函数

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

代码语言:javascript复制
val onClickDecrement = View.OnClickListener {
    // 方法体
}
代码语言:javascript复制
android:onClick="@{state.onClickDecrement}"

3.有参响应函数

代码语言:javascript复制
fun onReset(view: View) {
    // 方法体
}
代码语言:javascript复制
// 第一种写法
android:onClick="@{(view) -> state.onReset(view)}" 

// 第二种写法
android:onClick="@{state::onReset}"

4.2 @BindingAdapter自定义属性

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

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

  1. 顶级函数实现
代码语言:javascript复制
// 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型,正确的写法应该是下面这样:

  • 正确的写法
代码语言:javascript复制
<TextView
    style="@style/TextStyle.Title"
    android:layout_marginTop="16dp"
    app:customSize="@{25}"
    app:customTitle="@{state.title}" />
  • 常见错误的写法
代码语言:javascript复制
<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注解
代码语言:javascript复制
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。

代码语言:javascript复制
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。

代码语言:javascript复制
@BindingAdapter(value = ["name", "age"], requireAll = true)
fun TextView.setUserInfo(name: String, age: String) {
    text = "${name}今年${age}岁"
}

3.这时候使用DataBinding的时候,

0 人点赞