10、LiveData与DataBinding:DataBinding基础、LiveData与DataBinding双向绑定、简化UI更新代码。
好,咱们进入第10章。前面几章我们把LiveData和ViewModel单独拎出来讲透了,但说实话,在真实项目中,很少有人会手写一大堆observe回调来更新UI。那样太累了,代码也显得啰嗦。
这时候,DataBinding就该登场了。它能把数据和UI直接绑在一起,省掉中间那些繁琐的模板代码。我个人习惯是,只要项目里用了MVVM,DataBinding几乎是必选项。今天我们就来聊聊,怎么把LiveData和DataBinding结合起来,让UI更新变得像呼吸一样自然。
10.1 DataBinding基础:从配置到第一行绑定
DataBinding不是新东西了,但很多新手朋友对它又爱又怕。其实说白了,它就是Google提供的一个工具库,让你能在XML布局里直接写表达式,把数据“塞”进视图里。
10.1.1 环境配置
首先,你得在build.gradle里开启它。注意,是模块级别的那个文件:
android {
...
buildFeatures {
dataBinding = true
}
}
嗯,这里要注意,如果你用的是AGP 4.0以上的版本,这么写就对了。老版本可能还需要在gradle.properties里加一些配置,但现在基本不用了。
10.1.2 改造布局文件
开启之后,你的XML布局需要“升级”一下。把根布局用<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="@{String.valueOf(user.age)}" />
</LinearLayout>
</layout>
看到@{user.name}了吗?这就是DataBinding的核心语法。它会在运行时自动把user对象的name属性赋值给TextView。
10.1.3 在Activity/Fragment中绑定
布局改好了,接下来就是在代码里“激活”它。以前我们写setContentView(R.layout.activity_main),现在要换成:
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.user = User("张三", 25)
就这么简单。你给binding.user赋值,UI就会自动刷新。不过,这里有个坑——普通的Java Bean对象不具备“自动刷新”能力。你改了user.name,UI不会变。要想让它变,得用ObservableField或者LiveData。我们接着往下看。
10.2 LiveData与DataBinding双向绑定
好,基础讲完了。现在进入重头戏——LiveData和DataBinding的配合。为什么要把它们放一起?因为LiveData本身是“可观察的”,DataBinding又需要“可观察的数据源”,两者天生一对。
10.2.1 单向绑定:数据驱动UI
假设我们有一个ViewModel,里面暴露了一个LiveData:
class MainViewModel : ViewModel() {
val userName = MutableLiveData("默认名称")
}
在XML里,我们直接绑定这个LiveData:
<data>
<variable
name="viewModel"
type="com.example.MainViewModel" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.userName}" />
然后在Activity里,把ViewModel和binding关联起来:
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = viewModel
binding.lifecycleOwner = this // 这行很关键!
为什么要设置lifecycleOwner?因为DataBinding需要知道LiveData的生命周期,才能正确地添加和移除观察者。不写这行,LiveData不会自动更新UI。我曾经在一个项目里漏了这一行,调试了半天才发现,嗯,都是泪。
10.2.2 双向绑定:UI也能改数据
单向绑定是数据变了,UI跟着变。那如果用户输入内容,想反过来更新ViewModel里的数据呢?这就需要双向绑定了。
双向绑定的语法是@={},多了一个等号:
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.userName}" />
注意,这里要求userName必须是MutableLiveData,或者实现了Observable接口。因为双向绑定需要同时监听数据变化和UI变化。
TextView,用单向绑定就够了。双向绑定主要用于输入控件,比如EditText、CheckBox、SeekBar等。
10.3 简化UI更新代码:告别findViewById和observe
好了,现在我们来算一笔账。没有DataBinding的时候,你要写多少代码?
findViewById或者 ViewBinding 的binding.textViewviewModel.userName.observe(this) { textView.text = it }- 如果还有别的控件,重复第1、2步
有了DataBinding之后呢?
- 在XML里写
@{viewModel.userName} - 在Activity里设置
binding.lifecycleOwner = this - 完事。
你想想看,一个页面如果有十几个控件,这能省多少代码?而且代码的可读性也大大提升了——你一眼就能在XML里看到数据和视图的对应关系。
10.3.1 实战:一个完整的登录页面
我们来写一个简单的登录页面,感受一下这种“丝滑”的体验。
ViewModel:
class LoginViewModel : ViewModel() {
val username = MutableLiveData("")
val password = MutableLiveData("")
val loginResult = MutableLiveData<String>()
fun login() {
// 模拟登录
loginResult.value = "登录成功"
}
}
XML布局:
<layout>
<data>
<variable name="vm" type="com.example.LoginViewModel" />
</data>
<LinearLayout ...>
<EditText
android:text="@={vm.username}"
android:hint="用户名" />
<EditText
android:text="@={vm.password}"
android:hint="密码"
android:inputType="textPassword" />
<Button
android:onClick="@{() -> vm.login()}"
android:text="登录" />
<TextView
android:text="@{vm.loginResult}"
android:visibility="@{vm.loginResult != null ? View.VISIBLE : View.GONE}" />
</LinearLayout>
</layout>
Activity:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityLoginBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
binding.vm = LoginViewModel()
binding.lifecycleOwner = this
}
}
看到了吗?整个Activity里几乎没有UI更新的代码。所有的逻辑都在ViewModel里,UI只是被动地“展示”和“收集”数据。这就是MVVM的理想状态。
10.4 知识体系与核心逻辑
为了让你更直观地理解这一章的内容,我画了一张图。它展示了DataBinding、LiveData和ViewModel三者之间的关系,以及数据流动的方向。
10.5 总结与个人经验
这一章的内容其实就三个关键词:DataBinding基础、双向绑定、简化代码。我个人觉得,DataBinding最大的价值不是“少写几行代码”,而是改变了我们思考UI的方式。以前我们总想着“数据变了,我要去更新哪个控件”,现在只需要想“数据是什么,UI自然就是什么”。
我在项目中遇到过一种情况:团队里有人把DataBinding和LiveData用成了“万能药”,什么逻辑都往XML里塞,结果布局文件变得又臭又长。记住,DataBinding只负责绑定,复杂的业务逻辑还是要放在ViewModel或Repository里。
另外,双向绑定虽然好用,但要注意循环更新的问题。比如你在EditText的afterTextChanged里又去修改了LiveData的值,就可能触发无限循环。解决办法是:在ViewModel里做防抖,或者只在用户输入完成后才更新数据。
好了,这一章就到这里。下一章我们会聊聊更高级的话题——如何用LiveData配合Room数据库,实现数据的持久化和自动刷新。嗯,到时候见。