4、DataBinding基础:布局文件转换为DataBinding布局、数据对象与Observable、@{}语法与数据绑定、@BindingAdapter自定义属性
好,咱们今天来啃DataBinding这块硬骨头。说实话,我刚开始接触DataBinding的时候,心里是有点抵触的——总觉得XML里写表达式怪怪的。但用了一段时间后,我不得不承认:这玩意儿真香。它让UI和数据之间的绑定变得特别直观,代码量直接砍半。
这一章,我会带你从零开始,把DataBinding的四个核心知识点掰开揉碎。你跟着我走一遍,保证能上手。
4.1 布局文件如何变成DataBinding布局
先说说最基础的操作。普通的布局文件,根节点是各种ViewGroup。DataBinding布局呢,说白了就是在外层套一个<layout>标签。
嗯,就这么简单。但要注意,这个<layout>标签必须是最外层,不能有任何东西包着它。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}" />
</LinearLayout>
</layout>
看到<data>标签了吗?这里面就是声明变量用的。我习惯把需要用到的数据对象都放在这里,一目了然。
改完布局文件后,Android Studio会自动生成一个Binding类。比如你的布局叫activity_main.xml,那生成的类就是ActivityMainBinding。命名规则就是:下划线转驼峰,末尾加Binding。
4.2 数据对象与Observable
布局写好了,数据从哪来?你总得有个对象吧。咱们先定义一个普通的User类:
data class User(
val name: String,
val age: Int
)
然后你在Activity里这样绑定:
val binding = ActivityMainBinding.inflate(layoutInflater)
binding.user = User("张三", 25)
setContentView(binding.root)
跑起来看看?嗯,界面确实显示了。但问题来了——如果你改了user.name,UI会跟着变吗?
不会。因为普通的数据对象不具备通知能力。这就是为什么我们需要Observable。
DataBinding提供了几种现成的可观察类:
ObservableField<T>—— 单个字段ObservableBoolean、ObservableInt—— 基本类型专用ObservableArrayList<T>—— 列表ObservableArrayMap<K, V>—— 键值对
我个人最常用的是ObservableField。改造一下User类:
class User {
val name = ObservableField<String>()
val age = ObservableInt()
init {
name.set("张三")
age.set(25)
}
}
现在你改数据,UI会自动刷新。为什么?因为ObservableField内部实现了观察者模式,数据一变,绑定了它的View就会收到通知。
4.3 @{}语法与数据绑定
这个@{} 语法,是DataBinding的灵魂。它让你在XML里直接写表达式,把数据和View连起来。
基本用法:
android:text="@{user.name}"
android:visibility="@{user.age > 18 ? View.VISIBLE : View.GONE}"
android:onClick="@{() -> presenter.onUserClick(user)}"
你可以在@{} 里写:
- 属性引用:
@{user.name} - 三目运算:
@{age > 18 ? "成年" : "未成年"} - 方法调用:
@{user.getName()} - Lambda表达式:
@{() -> handler.onClick()} - 空合并运算符:
@{user.name ?? "默认名"}
我记得有一次,产品经理要求根据用户等级显示不同的背景色。用DataBinding一行搞定:
android:background="@{user.level == 1 ? @color/gold : @color/silver}"
你想想看,如果用传统方式,你得写多少行代码?findViewById、判断、setBackgroundColor……现在全没了。
@{} 里写太复杂的逻辑。比如循环、创建新对象、调用耗时方法。XML不是写业务逻辑的地方。我见过有人把几十行的计算塞进去,结果维护起来想哭。
4.4 @BindingAdapter自定义属性
有时候,系统自带的属性不够用。比如你想给ImageView加载网络图片,总不能写android:src="@{url}"吧?系统不认识url啊。
这时候@BindingAdapter就派上用场了。它让你自定义属性,想怎么玩就怎么玩。
来看一个加载图片的例子:
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
if (url != null) {
Glide.with(view.context)
.load(url)
.into(view)
}
}
然后在XML里:
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:imageUrl="@{user.avatarUrl}" />
看到app:imageUrl了吗?这就是我们自定义的属性。DataBinding会自动找到对应的@BindingAdapter方法,把值传进去。
你还可以定义多个属性组合:
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun loadImage(view: ImageView, url: String?, placeholder: Drawable?) {
Glide.with(view.context)
.load(url)
.placeholder(placeholder ?: ContextCompat.getDrawable(view.context, R.drawable.ic_default))
.into(view)
}
XML里这样用:
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:imageUrl="@{user.avatarUrl}"
app:placeholder="@{@drawable/ic_default}" />
我曾经在一个电商项目里,用@BindingAdapter封装了图片加载、圆角处理、占位图、加载失败图……十几个属性。团队其他人直接用,爽得不行。
requireAll = false表示这些属性不是必须同时出现。你只传imageUrl也行,只传placeholder也行。灵活度很高。
知识体系总览
下面这张图,把本章四个知识点的关系串起来了。你看一遍,心里就有谱了。
看到没?这四个知识点是环环相扣的。先改布局,再准备数据,然后用@{} 绑定,最后用@BindingAdapter扩展。一条线走下来,DataBinding的基础你就拿下了。
说实话,刚开始学的时候,你可能会觉得@BindingAdapter有点绕。但用多了你会发现,它其实是DataBinding最强大的地方——把那些重复的UI操作封装起来,一次编写,到处复用。
嗯,这一章的内容就到这。代码不多,但每个点都是实战中高频使用的。你先把这几个例子跑一遍,感受一下DataBinding的「自动感」。下一章咱们会深入更高级的用法。