目录
  1. 1. 一、DataBinding 架构原理
    1. 1.1. 1.1 编译期代码生成流程
    2. 1.2. 1.2 DataBinding 与 ViewBinding 的本质区别
  2. 2. 二、基础使用
    1. 2.1. 2.1 启用 DataBinding
    2. 2.2. 2.2 布局声明
    3. 2.3. 2.3 Activity/Fragment 中使用
  3. 3. 三、布局表达式详解
    1. 3.1. 3.1 表达式语法对照表
    2. 3.2. 3.2 空值安全
    3. 3.3. 3.3 事件绑定两种方式
  4. 4. 四、Observable 与数据变更通知
    1. 4.1. 4.1 ObservableField —— 单个字段级别
    2. 4.2. 4.2 BaseObservable —— 类级别(推荐)
    3. 4.3. 4.3 LiveData 自动绑定
  5. 5. 五、BindingAdapter:自定义属性绑定
    1. 5.1. 5.1 基础用法
    2. 5.2. 5.2 多属性 BindingAdapter
    3. 5.3. 5.3 接收旧值的 BindingAdapter
    4. 5.4. 5.4 RecyclerView 的 BindingAdapter
    5. 5.5. 5.5 BindingConversion —— 类型自动转换
  6. 6. 六、双向绑定(Two-way DataBinding)
    1. 6.1. 6.1 原理
    2. 6.2. 6.2 InverseBindingAdapter 自定义
    3. 6.3. 6.3 双向绑定与转换器
  7. 7. 七、与 ViewBinding 的对比与迁移
    1. 7.1. 7.1 ViewBinding 基础用法
    2. 7.2. 7.2 从 DataBinding 迁移到 ViewBinding
    3. 7.3. 7.3 决策树
  8. 8. 八、性能考虑与最佳实践
    1. 8.1. 8.1 DataBinding 性能开销
    2. 8.2. 8.2 表达式性能优化
    3. 8.3. 8.3 避免在布局表达式中写复杂逻辑
  9. 9. 面试常考问题
  10. 10. 九、DataBinding 与 Room 集成
  11. 11. 十、DataBinding 与 Kotlin Flow 集成
JetPack全家桶(九)之DataBinding数据绑定

DataBinding 是 Jetpack 中实现声明式 UI 绑定的库,它将数据对象直接绑定到布局文件,在编译期生成绑定类,消除 findViewById 样板代码,并支持双向绑定与自定义绑定适配器。

一、DataBinding 架构原理

1.1 编译期代码生成流程

DataBinding 的核心工作机制分为三步:

Step 1: XML 扫描
布局 XML → LayoutProcessor 扫描所有 <layout> 标签

Step 2: 注解处理 + 代码生成
注解处理器 → 生成 *Binding.java(如 ActivityMainBinding.java)

Step 3: 运行时绑定
Activity.setContentView() 或 DataBindingUtil.setContentView()
→ 调用生成的 Binding 类构造函数
→ 执行 executeBindings(),执行布局表达式

生成的 *Binding 类继承自 ViewDataBinding,包含:

  • 所有带 android:id 的 View 的强类型引用(等于自动 findViewById
  • executeBindings() 方法:执行布局表达式,计算绑定值并设置到 View
  • setVariable() / getVariable() 用于更新数据绑定变量

1.2 DataBinding 与 ViewBinding 的本质区别

很多人混淆两者,但它们的设计目标和实现方式完全不同:

维度 DataBinding ViewBinding
设计目标 数据驱动的声明式 UI 绑定 类型安全的 View 引用替代
布局表达式 支持 @{...} / @={...} 不支持
双向绑定 支持 不支持
生成方式 注解处理器(Annotation Processor) 布局文件直接生成
构建速度 较慢(需要处理注解) 快(简单的代码生成)
绑定适配器 支持自定义 @BindingAdapter 不支持
Observable 自动更新 支持 不支持
生成文件大小 大(包含所有表达式逻辑) 小(仅 View 引用)
适用场景 MVVM 中数据驱动 UI 仅需 View 类型安全引用

两者可以共存。 DataBinding 默认启用所有 ViewBinding 的功能——ActivityMainBinding 本身就直接包含了所有 View 引用。

二、基础使用

2.1 启用 DataBinding

// app/build.gradle.kts
android {
buildFeatures {
dataBinding = true
// viewBinding = true // 如果只用 ViewBinding,开启这个即可
}
}

2.2 布局声明

DataBinding 布局必须用 <layout> 作为根标签,内含 <data> 块和视图层级:

<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>
<!-- 导入 Java/Kotlin 类,用于表达式 -->
<import type="android.view.View" />
<import type="com.example.util.DateUtils" />
<import type="com.example.model.User" />

<!-- 声明变量,类型可以是导入的类或全限定类名 -->
<variable
name="user"
type="com.example.model.User" />

<variable
name="viewModel"
type="com.example.ui.UserViewModel" />

<variable
name="handler"
type="com.example.ui.ClickHandler" />
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<!-- 文本绑定 -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:textSize="18sp" />

<!-- 条件表达式 -->
<TextView
android:text="@{user.isVip ? @string/vip_label : @string/normal_label}"
android:textColor="@{user.isVip ? @color/red : @color/gray}"
android:visibility="@{user.isVip ? View.VISIBLE : View.GONE}"
... />

<!-- 方法调用 -->
<TextView
android:text="@{DateUtils.formatDate(user.registerDate)}"
... />

<!-- 空值安全:DataBinding 自动处理 null -->
<TextView
android:text="@{user.address ?? user.city ?? `未知城市`}"
... />

<!-- 事件绑定:方法引用(推荐:编译期检查) -->
<Button
android:onClick="@{handler::onSaveClick}"
... />

<!-- 事件绑定:监听器绑定(Lambda,运行时灵活) -->
<Button
android:onClick="@{() -> viewModel.onDeleteUser(user)}"
... />

<!-- 双向绑定:@= 符号 -->
<EditText
android:text="@={viewModel.searchQuery}"
... />

<!-- 图片加载(自定义 BindingAdapter) -->
<ImageView
app:imageUrl="@{user.avatarUrl}"
app:placeholder="@{@drawable/ic_avatar_placeholder}"
... />

</LinearLayout>
</layout>

2.3 Activity/Fragment 中使用

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 方式 1:DataBindingUtil(标准方式)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

// 方式 2:如果基类已设置,可以直接用生成的 inflate
// binding = ActivityMainBinding.inflate(layoutInflater)

// 设置变量
binding.user = User(name = "张三", isVip = true, registerDate = Date())
binding.viewModel = viewModel
binding.handler = ClickHandler()

// 为 LiveData 绑定设置生命周期所有者(LiveData 自动观察)
binding.lifecycleOwner = this
}
}

// 在 Fragment 中
class UserFragment : Fragment() {

private var _binding: FragmentUserBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentUserBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null // 防止内存泄露
}
}

三、布局表达式详解

3.1 表达式语法对照表

DataBinding 的布局表达式语言是 Java 表达式语法的子集,支持以下特性:

表达式类型 语法 示例
属性访问 obj.property @{user.name}
null 合并 ?? @{user.name ?? "匿名用户"}
三元运算 ? : @{user.isVip ? "VIP" : "普通"}
字符串拼接 + @{"用户: " + user.name}
字符串模板 ` @{`用户: ${user.name}`}
算术运算 + - * / % @{price * quantity}
比较运算 == != < > <= >= @{age >= 18}
逻辑运算 && || ! @{isLoggedIn && hasPermission}
位运算 & | ^ @{flags & MASK}
类型转换 (Type) expr @{(User)obj.name}
方法调用 obj.method(args) @{DateUtils.format(date)}
数组访问 [] @{items[0]}
集合字面量 [] @{[1, 2, 3]}
Map 访问 [] @{map["key"]}
资源引用 @string/ @dimen/ @{@string/hello}
实例判断 instanceof 不支持(用类型转换代替)
new 操作 new 不支持
Lambda () -> ... @{() -> handler.onClick()}

3.2 空值安全

DataBinding 自动生成 null 检查代码,避免 NPE:

<!-- 布局中的表达式 -->
<TextView android:text="@{user.name}" />

<!-- DataBinding 生成的等价 Java 代码 -->
if (user != null && user.getName() != null) {
textView.setText(user.getName());
}

使用 ?? 提供默认值:

android:text="@{user.nickname ?? user.name ?? `匿名用户`}"

3.3 事件绑定两种方式

方法引用(Method Reference):编译期验证方法签名

<Button android:onClick="@{handler::onUserClick}" />
class ClickHandler {
// 方法签名必须与事件监听器一致
fun onUserClick(view: View) {
// 处理点击
}
}

监听器绑定(Listener Binding):灵活的 Lambda 表达式

<Button android:onClick="@{() -> viewModel.onItemClick(user)}" />
<Button android:onClick="@{(view) -> viewModel.onItemClick(user, view)}" />
<EditText android:onTextChanged="@{(text, start, before, count) -> viewModel.onSearch(text.toString())}" />

四、Observable 与数据变更通知

4.1 ObservableField —— 单个字段级别

class User {
val name = ObservableField<String>()
val age = ObservableInt()
val isVip = ObservableBoolean()
val avatarUrl = ObservableField<String>()
}

// 更新:直接在字段上调用 set()
user.name.set("李四") // UI 自动更新
user.age.set(25) // UI 自动更新
user.isVip.set(false) // UI 自动更新

原理:ObservableField.set() 内部调用 notifyChange(),触发 Binding 类的 executeBindings() 重新执行。

4.2 BaseObservable —— 类级别(推荐)

class User : BaseObservable() {
@get:Bindable
var name: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.name) // 仅通知 name 属性变更
}

@get:Bindable
var age: Int = 0
set(value) {
field = value
notifyPropertyChanged(BR.age)
}

@get:Bindable
var isVip: Boolean = false
set(value) {
field = value
notifyPropertyChanged(BR.isVip)
}
}

使用 @Bindable 注解后,编译期生成 BR 类(Binding Resource),包含所有被标记属性的常量。

4.3 LiveData 自动绑定

当给 binding.lifecycleOwner 赋值后,LiveData 自动观察:

class UserViewModel : ViewModel() {
val userName = MutableLiveData("张三")
val isVip = MutableLiveData(false)

fun updateName(newName: String) {
userName.value = newName // UI 自动更新,无需手动 setVariable
}
}
<TextView android:text="@{viewModel.userName}" />

LiveData 的优势:生命周期感知 —— 在配置变更时自动恢复绑定,在 Fragment 不可见时暂停更新。

五、BindingAdapter:自定义属性绑定

5.1 基础用法

// 全局注册 BindingAdapter(可以放在任意 Kotlin 文件中)
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
if (!url.isNullOrEmpty()) {
Glide.with(view.context)
.load(url)
.into(view)
}
}

// 使用
// app:imageUrl="@{user.avatarUrl}"

5.2 多属性 BindingAdapter

// 需要同时设置 imageUrl 和 placeholder 时才调用
@BindingAdapter(value = ["imageUrl", "placeholder", "errorImage"], requireAll = false)
fun loadImage(
view: ImageView,
url: String?,
placeholder: Drawable?,
errorImage: Drawable?
) {
Glide.with(view.context)
.load(url)
.placeholder(placeholder ?: ColorDrawable(Color.GRAY))
.error(errorImage)
.into(view)
}
<ImageView
app:imageUrl="@{user.avatarUrl}"
app:placeholder="@{@drawable/ic_placeholder}"
app:errorImage="@{@drawable/ic_error}" />

5.3 接收旧值的 BindingAdapter

// 需要对比新旧值决定是否执行(避免重复加载)
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, oldUrl: String?, newUrl: String?) {
if (oldUrl != newUrl) { // 只在新 URL 与旧 URL 不同时才加载
Glide.with(view.context)
.load(newUrl)
.into(view)
}
}

5.4 RecyclerView 的 BindingAdapter

@BindingAdapter("items", "itemLayout", requireAll = false)
fun setRecyclerViewItems(
recyclerView: RecyclerView,
items: List<Any>?,
@LayoutRes itemLayout: Int = 0
) {
items ?: return

// 假设 AdaptiveAdapter 是一个泛型适配器
if (recyclerView.adapter == null) {
recyclerView.adapter = GenericAdapter(itemLayout)
}
(recyclerView.adapter as GenericAdapter).submitList(items.toList())
}
<androidx.recyclerview.widget.RecyclerView
app:items="@{viewModel.items}"
app:itemLayout="@{@layout/item_user}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

5.5 BindingConversion —— 类型自动转换

// 自动将 Date 转换为 String
@BindingConversion
fun convertDateToString(date: Date?): String? {
return date?.let { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(it) }
}

// 使用时无需手动调用转换函数
// <TextView android:text="@{user.registerDate}" />
// DataBinding 自动将 Date → String

六、双向绑定(Two-way DataBinding)

6.1 原理

双向绑定使用 @={} 语法。当用户编辑 EditText 时,值的变化自动回写到绑定的变量:

<EditText
android:text="@={viewModel.userName}" />
<CheckBox
android:checked="@={viewModel.isAgreed}" />
<SeekBar
android:progress="@={viewModel.volume}" />

6.2 InverseBindingAdapter 自定义

当内置的双向绑定类型不满足需求时,自定义反向绑定:

// 日期选择器 → Date 对象双向绑定
@BindingAdapter("selectedDate")
fun setSelectedDate(view: DatePicker, date: Date?) {
date?.let {
view.init(
it.year + 1900,
it.month,
it.date
) { _, year, month, dayOfMonth ->
// 变化时触发反向绑定
val newDate = Date(year - 1900, month, dayOfMonth)
InverseBindingAdapter
}
}
}

@InverseBindingAdapter(attribute = "selectedDate", event = "dateAttrChanged")
fun getSelectedDate(view: DatePicker): Date {
return Date(view.year - 1900, view.month, view.dayOfMonth)
}

@BindingAdapter("dateAttrChanged")
fun setDateChangeListener(view: DatePicker, listener: InverseBindingListener?) {
view.init(view.year, view.month, view.dayOfMonth) { _, year, month, day ->
listener?.onChange() // 通知 DataBinding 值已变更
}
}

6.3 双向绑定与转换器

// 场景:EditText 输入的是 String,但 ViewModel 中存的是 Int
@BindingAdapter("android:text")
fun setInt(view: TextView, value: Int) {
view.text = value.toString()
}

@InverseBindingAdapter(attribute = "android:text")
fun getInt(view: TextView): Int {
return view.text.toString().toIntOrNull() ?: 0
}

七、与 ViewBinding 的对比与迁移

7.1 ViewBinding 基础用法

// app/build.gradle.kts
android {
buildFeatures {
viewBinding = true
}
}

// Activity 中使用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

// 直接使用 View 引用
binding.tvName.text = "张三"
binding.btnSave.setOnClickListener { save() }
}
}

7.2 从 DataBinding 迁移到 ViewBinding

如果当前使用 DataBinding 但没有用到布局表达式、双向绑定等高级功能,可以迁移到 ViewBinding 以获得更快的构建速度:

  1. 将布局文件从 <layout> 包裹改为普通布局根标签
  2. 移除 <data>
  3. @{...} 表达式改为代码中的手动赋值
  4. buildFeatures.dataBinding = true 改为 buildFeatures.viewBinding = true
  5. 生成类名从 ActivityMainBinding 保持不变(只保留 View 引用部分)

7.3 决策树

需要自动数据-UI 同步?
├── 是 → 需要双向绑定?
│ ├── 是 → DataBinding(@={})
│ └── 否 → 用 Compose 还是 View?
│ ├── Compose → 无需 DataBinding
│ └── View → DataBinding 或 ViewModel + LiveData/StateFlow + 手动绑定
└── 否 → 仅需要 View 引用?→ ViewBinding
└── 是 → ViewBinding
└── 否 → 用 Compose

八、性能考虑与最佳实践

8.1 DataBinding 性能开销

  1. 构建时间:DataBinding 需要注解处理器扫描所有布局文件,大型项目可能额外增加 10-30% 的编译时间。
  2. APK 大小:生成的 Binding 类会增加 dex 方法数(每个布局文件生成数百到数千行代码)。
  3. 运行时开销executeBindings() 在首次绑定和每次数据变更时执行,如果布局表达式过于复杂,可能影响帧率。

8.2 表达式性能优化

<!-- 坏:每次数据变更都执行方法调用 -->
<TextView android:text="@{complexFormatFunction(user.profile)}" />

<!-- 好:在 ViewModel 中预计算,布局只做属性访问 -->
<TextView android:text="@{viewModel.formattedProfile}" />

8.3 避免在布局表达式中写复杂逻辑

<!-- 坏:业务逻辑写进布局 -->
<TextView
android:visibility="@{user.age > 18 && user.isVip && !user.isBanned ? View.VISIBLE : View.GONE}" />

<!-- 好:在 ViewModel 或 Presenter 中封装 -->
<TextView
android:visibility="@{viewModel.shouldShowVipBadge ? View.VISIBLE : View.GONE}" />

面试常考问题

Q1:DataBinding 的核心原理?

DataBinding 在编译时扫描 <layout> 标签包裹的 XML 文件,通过注解处理器(DataBindingProcessor)生成 *Binding 类(继承自 ViewDataBinding)。该类持有所有 View 引用,并在 executeBindings() 方法中执行布局表达式。数据变更通过 Observable 接口(ObservableField / BaseObservable)回调触发绑定重新执行。源码入口:androidx.databinding.DataBindingUtil

Q2:@BindingAdapter 的 override 与 requireAll 参数有何作用?

  • requireAll = false:当绑定适配器的多个属性中有部分未设置时,仍然调用适配器方法。默认为 true,需要所有属性都设置才调用。
  • override 参数(通过 @BindingAdapteroverride 属性或 @BindingMethods):决定多个同名 BindingAdapter 的优先级覆盖策略,解决全局和局部适配器的冲突。

Q3:DataBinding 如何避免 null 安全导致的崩溃?

DataBinding 生成的代码在 setter 调用前自动进行 null 检查。例如 android:text="@{user.name}" 生成的等价代码:

if (user != null && user.getName() != null) {
textView.setText(user.getName());
}

布局表达式中的 ?? 操作符提供 null 合并默认值。@BindingAdapter 的参数默认 nullable,可通过 @NonNull 注解限制。

Q4:BaseObservable 与 ObservableField 如何选择?

  • ObservableField:适合字段少、简单的数据类。不侵入原有类结构。但每个字段需独立的 ObservableField 包装。
  • BaseObservable:适合字段多、需要精确控制通知粒度的类。需覆写 setter 调用 notifyPropertyChanged(BR.fieldName)。与 Room Entity 集成更好(Entity 可直接继承 BaseObservable)。
  • 对已有不可修改的数据类(如第三方的 data class),使用 ObservableField 包装。
  • 在 MVVM 架构中,如有 ViewModel 持有 LiveData/StateFlow,通常不需要额外的 ObservableFieldBaseObservable,直接将 ViewModel 绑定到布局即可。

Q5:DataBinding、ViewBinding、findViewById 和 Kotlin Synthetics 的演进关系?

  1. findViewById(API 1+):运行时反射查找,类型不安全,大量样板代码。已淘汰。
  2. Kotlin Synthetics(Kotlin Android Extensions):编译期生成,曾被广泛使用,但仅支持 Kotlin、不支持跨模块、废弃于 2020 年。已从 Kotlin 1.8+ 移除。
  3. ViewBinding(AGP 3.6+):编译期生成类型安全的 View 引用,轻量快速。当前推荐用于仅需 View 引用的场景。
  4. DataBinding(Support Library 时代起):完整的声明式数据绑定框架。适合 MVVM 中需要数据驱动 UI 的场景。
  5. Jetpack Compose:声明式 UI 框架,彻底代替 ViewBinding/DataBinding。未来方向。

Q6:在 include 布局中如何传递变量?

<!-- 主布局 -->
<layout>
<data>
<variable name="user" type="com.example.User" />
</data>
<include layout="@layout/item_user_detail"
app:user="@{user}"
app:showHeader="@{true}" />
</layout>

<!-- item_user_detail.xml 的 <data> 中声明同名变量 -->
<layout>
<data>
<variable name="user" type="com.example.User" />
<variable name="showHeader" type="boolean" />
</data>
...
</layout>

Q7:DataBinding 如何处理 RecyclerView 中的 item 绑定?

class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
var items: List<User> = emptyList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val binding = ItemUserBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return UserViewHolder(binding)
}

override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(items[position])
}

class UserViewHolder(private val binding: ItemUserBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(user: User) {
binding.user = user
binding.executePendingBindings() // 立即执行绑定,避免 item 回收导致的闪烁
}
}
}

executePendingBindings() 在 RecyclerView 中尤为重要:它强制在当前帧立即执行绑定计算,避免 View 回收复用时前一帧的脏数据短暂显示。

Q8:DataBinding 生成的代码中有哪些关键方法?

每个 *Binding 类包含:

方法 说明
inflate(LayoutInflater, ...) 从 XML 创建绑定实例
bind(View) 绑定到已存在的 View 树
setVariable(int, Object) 根据 BR id 设置变量
executeBindings() 执行所有表达式计算和绑定
invalidateAll() 标记所有绑定为脏,触发下次重新计算
setLifecycleOwner(LifecycleOwner) 设置 LiveData 的生命周期观察者

生成的代码中每个 View 的表达式绑定逻辑类似于:

@Override
protected void executeBindings() {
String userName = null;
boolean isVip = false;
User user = mUser;

if (user != null) {
userName = user.getName();
isVip = user.getIsVip();
}

// 只更新实际变化的 View,避免不必要的 layout request
if (userName != null && !userName.equals(mBoundView0textView.getText().toString())) {
mBoundView0textView.setText(userName);
}
}

Q9:DataBinding 在大型项目中的构建优化策略?

  1. 将 DataBinding 模块化:每个 Feature 模块独立启用 dataBinding = true,避免单模块过大导致全量重建
  2. 使用 Gradle Build Cache:在所有 CI 机器间共享 DataBinding 的注解处理器输出
  3. 增量注解处理:AGP 4.1+ 默认开启增量注解处理,避免修改一个布局导致所有 Binding 类重建
  4. 将复杂的布局表达式提取到 ViewModel:布局表达式越简单,生成的 Binding 类越小
  5. 考虑迁移到 Compose:对于新页面使用 Compose,绕过 DataBinding 的注解处理开销

九、DataBinding 与 Room 集成

当 Room Entity 继承 BaseObservable 并使用 @Bindable 注解字段时,可以实现数据库变更自动驱动 UI 更新:

@Entity(tableName = "users")
class User : BaseObservable() {
@PrimaryKey
var id: Int = 0

@ColumnInfo(name = "name")
@get:Bindable
var name: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.name)
}

@ColumnInfo(name = "is_vip")
@get:Bindable
var isVip: Boolean = false
set(value) {
field = value
notifyPropertyChanged(BR.isVip)
}
}

// 在 ViewModel 中观察 Room 的 Flow,更新 DataBinding 变量
@HiltViewModel
class UserViewModel @Inject constructor(
private val userDao: UserDao
) : ViewModel() {
val user = userDao.getCurrentUser()
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
}

// Fragment 中
viewLifecycleOwner.lifecycleScope.launch {
viewModel.user.collect { user ->
binding.user = user // 每次 user 变更,DataBinding 自动更新所有绑定
}
}

这种模式在没有任何 Compose/LiveData 的纯 DataBinding 项目中非常有效。

十、DataBinding 与 Kotlin Flow 集成

从 DataBinding 4.0+ 开始,布局表达式原生支持 StateFlow

class UserViewModel : ViewModel() {
private val _userName = MutableStateFlow("")
val userName: StateFlow<String> = _userName.asStateFlow()

private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
}
<!-- 设置 lifecycleOwner 后,StateFlow 也能自动观察 -->
<TextView
android:text="@{viewModel.userName}" />
<ProgressBar
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />

Flow 与 LiveData 在 DataBinding 中的行为一致:设置 lifecycleOwner 后,框架自动在生命周期活跃时收集 Flow 的当前值并更新 UI。

打赏
  • 微信
  • 支付宝

评论