DataBinding
是 Google 在 Jetpack 中推出的一款数据绑定的支持库,利用该库可以实现在页面组件和数据的双向绑定,类似与MVVM。
核心原理分析
DataBinding本质是Android编译期注解处理器 + 自动生成代码 的框架,核心目标是将XML布局中的UI组件与数据源(如ViewModel、实体类)建立绑定关系,实现数据变化自动刷新UI、UI事件反向通知数据的双向绑定,避免手动调用findViewById和setText/setOnClickListener等冗余代码。
简单来说,DataBinding的工作流程可以概括为:编译期解析XML生成绑定类 → 运行期初始化绑定类关联View和数据 → 通过观察者模式监听数据变化并刷新UI。
1. 编译期:核心是注解处理器生成绑定代码
这是DataBinding最核心的环节,所有“魔法”的起点都在编译阶段。
1.1 关键前置:开启DataBinding的作用
当你在build.gradle中设置buildFeatures { dataBinding true }时,Android Gradle插件会:
- 启用DataBinding注解处理器(
androidx.databinding:databinding-compiler); - 扫描所有带
<layout>根标签的XML布局文件; - 为每个符合条件的XML生成对应的
Binding类(如activity_main.xml→ActivityMainBinding)。
1.2 XML解析与代码生成流程
以一个简单的布局文件为例:
<!-- activity_main.xml -->
<layout>
<data>
<variable name="user" type="com.example.User" />
</data>
<TextView android:id="@+id/tv_name" android:text="@{user.name}" />
</layout>
编译期会执行以下步骤:
解析XML结构:
- 识别
<layout>根标签,解析<data>中的变量(如user)和表达式(如@{user.name}); - 解析UI组件的id(如
tv_name)和绑定表达式的关联关系。
- 识别
生成Binding类: 自动在
build/generated/data_binding_base_class_source_out/目录下生成ActivityMainBinding类,核心代码大致如下(简化版):public class ActivityMainBinding extends ViewDataBinding { // 自动生成的View引用(替代findViewById) public final TextView tvName; // 绑定的数据源 private User mUser; // 数据观察者(用于监听数据变化) private InverseBindingListener mUserListener; // 构造方法:初始化View引用 protected ActivityMainBinding(LayoutInflater inflater, View root) { super(inflater, root); // 自动生成findViewById逻辑,无需手动调用 this.tvName = root.findViewById(R.id.tv_name); // 初始化表达式映射 initExpressions(); } // 静态方法:创建Binding实例(我们常用的inflate方法) public static ActivityMainBinding inflate(LayoutInflater inflater) { return inflate(inflater, null, false); } // 设置绑定的数据源 public void setUser(User user) { this.mUser = user; // 触发UI刷新 notifyPropertyChanged(BR.user); } // 核心方法:根据数据更新UI @Override protected void executeBindings() { synchronized (this) { final User user = mUser; String user_name = user != null ? user.getName() : null; // 将数据赋值给View(替代手动tvName.setText()) tvName.setText(user_name); } } }生成BR类: 同时会生成
BR类(类似R类),里面是所有绑定变量的常量标识,用于标记“哪个数据发生了变化”:public class BR { public static final int _all = 0; public static final int user = 1; public static final int name = 2; }
你想了解DataBinding编译时对XML的拆分逻辑,以及生成的“两个文件+三个类”的具体内容,这是理解DataBinding编译原理的关键细节,我会逐一拆解清楚(注:不同Android Gradle插件版本命名略有差异,但核心逻辑一致)。
2. 编译期:核心前提
当你编写带<layout>根标签的DataBinding布局(如activity_main.xml)后,编译期会先将XML拆分为两个物理文件,再基于这两个文件生成三个核心类,最终完成数据绑定的代码支撑。
- 两个拆分的XML文件:
layout-xxx.xml:纯布局结构(无绑定逻辑),供Android系统加载UI;xxx-layoutinfo.xml:绑定元数据(变量、表达式),供注解处理器生成代码。
- 三个生成的核心类:
BR类:绑定变量/字段的标识常量,用于数据变化通知;XXXBinding类:核心执行类,封装View引用、数据绑定、UI刷新;DataBinderMapper类:布局ID与Binding类的映射器,负责找到对应Binding类。
这一套编译期的拆分和生成逻辑,最终实现了“编写简洁的XML绑定表达式” → “自动生成繁琐的findViewById和数据赋值代码”,这也是DataBinding能简化开发的核心原因。
2.1 编译期拆分的两个XML文件
DataBinding会把你编写的“一体化布局XML”拆分为纯布局结构文件和绑定表达式文件,目的是分离“UI结构”和“数据逻辑”,便于后续解析和代码生成。
2.1.1. 第一个文件:纯布局结构文件(layout-xxx.xml)
路径:
build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml作用:剥离
<data>标签和@{}绑定表达式,只保留纯Android原生布局结构(和普通XML布局完全一致)。示例: 你编写的原XML:
<!-- res/layout/activity_main.xml --> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.User" /> </data> <LinearLayout> <TextView android:id="@+id/tv_name" android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </layout>拆分后的纯布局文件:
<!-- activity_main-layout.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>可以看到:
<layout>、<data>标签被移除,android:text="@{user.name}"被清空(仅保留原生布局属性)。
2.1.2. 第二个文件:绑定表达式信息文件(xxx-layoutinfo.xml)
路径:
build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layoutinfo.xml作用:存储XML中的绑定元数据(变量定义、表达式、View与数据的关联关系),是后续生成Binding类的核心依据。
示例(简化版):
<LayoutDirectory> <Layout layout="activity_main"> <Variables> <Variable name="user" type="com.example.User" /> </Variables> <Views> <View id="@+id/tv_name" type="android.widget.TextView"> <Expressions> <Expression attribute="android:text" text="user.name"> <Location startLine="12" startCol="20" endLine="12" endCol="35"/> </Expression> </Expressions> </View> </Views> </Layout> </LayoutDirectory>这个文件会记录:
- 定义的变量(
user)及其类型; - 每个View的id、类型;
- 绑定表达式(如
tv_name的android:text对应user.name); - 表达式在原XML中的位置(便于编译报错时定位)。
- 定义的变量(
2.2 编译期生成的三个核心类
基于上述两个拆分后的XML文件,DataBinding注解处理器会生成三个核心类,共同支撑运行期的数据绑定逻辑:
2.2.1. 第一个类:BR类(Binding Resource)
路径:
build/generated/source/br/debug/com/example/BR.java作用:类似R类,是绑定变量/字段的唯一标识常量类,用于标记“哪个数据发生了变化”,是观察者模式的核心标识。
生成逻辑:解析
layoutinfo.xml中的变量、@Bindable注解的字段,为每个绑定项生成int类型常量。示例代码:
package com.example; public class BR { public static final int _all = 0; // 通用标识:所有数据变化 public static final int user = 1; // 对应XML中定义的user变量 public static final int name = 2; // 对应User类中@Bindable注解的name字段 }关键用途:
notifyPropertyChanged(BR.name)就是通过这个常量告诉DataBinding“name字段变了,需要刷新UI”。
2.2.2. 第二个类:XXXBinding类(核心绑定类)
路径:
build/generated/data_binding_base_class_source_out/debug/out/com/example/databinding/ActivityMainBinding.java命名规则:布局文件名驼峰化 + Binding(如
activity_main.xml→ActivityMainBinding)。父类:
androidx.databinding.ViewDataBinding(所有Binding类的基类)。作用:核心执行类,封装了View引用、数据绑定、UI刷新的所有逻辑。
核心内容(简化版):
package com.example.databinding; public class ActivityMainBinding extends ViewDataBinding { // 自动生成的View引用(替代findViewById) public final TextView tvName; // 绑定的数据源 private com.example.User mUser; // 构造方法:初始化View引用 + 绑定表达式 protected ActivityMainBinding(androidx.databinding.DataBindingComponent bindingComponent, View root) { super(bindingComponent, root, 1); // 1对应BR.user this.tvName = root.findViewById(com.example.R.id.tv_name); // 初始化表达式映射 setRootTag(root); invalidateAll(); } // 静态创建方法(你常用的inflate/inflate方法) public static ActivityMainBinding inflate(LayoutInflater inflater) { return inflate(inflater, null, false); } // 设置数据源的setter方法 public void setUser(com.example.User user) { this.mUser = user; // 标记数据变化,触发UI刷新 notifyPropertyChanged(BR.user); } // 核心方法:执行绑定逻辑(刷新UI) @Override protected void executeBindings() { synchronized (this) { final com.example.User user = mUser; String user_name = null; if (user != null) { user_name = user.getName(); // 获取数据 } // 将数据赋值给View(替代手动tvName.setText()) this.tvName.setText(user_name); } } }关键逻辑:
inflate():创建Binding实例 + 加载布局;setUser():设置数据源 + 通知数据变化;executeBindings():核心刷新逻辑,将数据赋值给View。
2.2.3. 第三个类:DataBinderMapper类(绑定映射类)
路径:
build/generated/data_binding_component_source_out/debug/out/com/example/DataBinderMapperImpl.java父类:
androidx.databinding.DataBinderMapper。作用:全局映射器,负责“布局ID → XXXBinding类”的匹配,是DataBinding内部找到对应Binding类的核心。
核心内容(简化版):
package com.example; public class DataBinderMapperImpl extends DataBinderMapper { @Override public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) { // 根据布局ID匹配对应的Binding类 switch (layoutId) { case com.example.R.layout.activity_main: return ActivityMainBinding.bind(component, view); default: return null; } } @Override public ViewDataBinding getDataBinder(DataBindingComponent component, LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent) { switch (layoutId) { case com.example.R.layout.activity_main: return ActivityMainBinding.inflate(inflater, parent, attachToParent); default: return null; } } }关键用途:当你调用
DataBindingUtil.setContentView(activity, R.layout.activity_main)时,底层就是通过这个类找到ActivityMainBinding并创建实例。
3. 运行期:绑定初始化与数据刷新
编译期生成的代码,最终在运行期完成“数据→UI”的绑定和刷新,核心分为3步:
3.1 初始化Binding类
这是你在Activity/Fragment中手动调用的代码,本质是创建编译期生成的Binding实例,并关联布局根View:
// Activity中初始化
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 绑定数据源
binding.user = User("张三")
此时binding.setUser()会触发executeBindings()方法,将user.name的值赋值给tvName.setText(),完成首次UI渲染。
3.2 数据变化监听:观察者模式
DataBinding实现“数据变→UI自动更”的核心是观察者模式,分为两种场景:
场景1:BaseObservable(手动通知)
如果你的数据类继承BaseObservable并给字段加@Bindable注解,DataBinding会为其添加观察者:
class User : BaseObservable() {
@Bindable // 标记该字段可绑定
var name: String = ""
set(value) {
field = value
// 通知DataBinding:name字段变化了
notifyPropertyChanged(BR.name)
}
}
当你修改user.name = "李四"时,notifyPropertyChanged(BR.name)会触发Binding类的executeBindings(),重新执行tvName.setText(user.name),UI自动刷新。
场景2:LiveData/Flow(自动通知)
如果绑定的是LiveData,DataBinding会自动观察LiveData的变化:
<!-- XML中绑定LiveData -->
<variable name="userLiveData" type="androidx.lifecycle.LiveData<com.example.User>" />
<TextView android:text="@{userLiveData.name}" />
运行期Binding类会调用userLiveData.observe(lifecycleOwner, observer),当LiveData数据变化时,观察者会触发executeBindings()刷新UI。
3.3 双向绑定原理(可选)
双向绑定(如@={user.name})是单向绑定的反向扩展,核心是:
- UI事件(如EditText输入)触发数据更新:DataBinding会为View设置监听(如
addTextChangedListener); - 数据更新后再通过单向绑定刷新UI,形成“UI→数据→UI”的闭环。
以EditText为例,编译期生成的代码会包含:
// 双向绑定的核心逻辑(简化)
editText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
// 将UI输入的内容赋值给数据
mUser.setName(s.toString());
// 通知数据变化(触发UI刷新,保证一致性)
notifyPropertyChanged(BR.name);
}
});
4. 关键细节:DataBinding的核心类
理解以下核心类,能更清晰掌握原理:
| 类名 | 作用 |
|---|---|
ViewDataBinding | 所有自动生成的Binding类的父类,封装了绑定初始化、数据刷新的核心逻辑 |
DataBinderMapper | 管理所有Binding类的映射关系,用于根据布局ID找到对应的Binding类 |
BR | 编译期生成的常量类,标记绑定变量的唯一标识 |
Observable/BaseObservable | 数据类的基类,实现观察者模式,用于通知数据变化 |
InverseBindingAdapter | 注解,用于定义UI事件到数据的反向绑定逻辑(双向绑定核心) |
5. 总结
- 编译期核心:DataBinding通过注解处理器解析带
<layout>的XML,自动生成XXXBinding类和BR类,替代findViewById和手动赋值逻辑; - 运行期核心:通过
ViewDataBinding初始化View与数据的关联,基于观察者模式监听数据变化,调用executeBindings()自动刷新UI; - 双向绑定本质:在单向绑定基础上,为View添加事件监听,将UI输入反向同步到数据,再通过单向绑定刷新UI。
ViewBinding 和 DataBinding 对比
1. 核心区别
| 特性 | ViewBinding | DataBinding |
|---|---|---|
| 核心定位 | 仅解决 View 绑定问题(替代 findViewById) | 不仅绑定 View,还支持数据双向绑定 |
| 学习成本 | 极低(几乎无额外学习成本) | 较高(需学表达式、生命周期、绑定规则) |
| 功能范围 | 仅 View 引用、空安全、类型安全 | 包含 ViewBinding 所有功能 + 数据绑定、表达式、双向绑定 |
| 编译速度 | 快(仅生成 View 绑定类) | 慢(额外解析布局中的表达式、绑定逻辑) |
| 布局文件要求 | 无特殊要求(普通 XML) | 需用<layout>根标签包裹,支持特殊表达式 |
2. 详细解析与使用场景
2.1 ViewBinding(视图绑定)
核心作用:自动生成对应布局的绑定类,通过类直接获取View控件,彻底替代findViewById,解决空指针、类型转换错误问题。
使用场景(优先选):
- 绝大多数常规页面(Activity/Fragment/Dialog),仅需简单的View操作(如setText、setOnClickListener);
- 追求开发效率和编译速度,不想引入复杂逻辑;
- 项目架构为MVC/MVP,数据更新需要手动触发(而非自动绑定);
- 团队以新手为主,降低学习和维护成本。
使用示例: 2.1.1 开启ViewBinding(build.gradle):
android {
...
buildFeatures {
viewBinding true // 仅开启ViewBinding
}
}
2.1.2 布局文件(activity_main.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
<TextView
android:id="@+id/tv_name"
... />
<Button
android:id="@+id/btn_click"
... />
</LinearLayout>
2.1.3 Activity中使用:
public class MainActivity extends AppCompatActivity {
// 自动生成的绑定类(布局名首字母大写 + Binding)
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化绑定类,替代setContentView
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 直接通过binding获取控件,无需findViewById
binding.tvName.setText("孙悟空");
binding.btnClick.setOnClickListener(v -> {
Toast.makeText(this, "点击了按钮", Toast.LENGTH_SHORT).show();
});
}
}
2.2 DataBinding(数据绑定)
核心作用:在ViewBinding基础上,实现数据与UI的自动绑定(单向/双向),无需手动调用setText/setImage等方法,是MVVM架构的核心基础。
使用场景(按需选):
- 项目架构为MVVM(如配合ViewModel/LiveData),需要数据自动驱动UI更新;
- 页面有大量数据需要实时更新(如表单、直播弹幕、数据列表);
- 希望减少“数据更新→手动调用View方法”的重复代码;
- 团队有一定经验,能维护复杂的绑定逻辑。
使用示例: 2.2.1 开启DataBinding(build.gradle):
android {
...
buildFeatures {
dataBinding true // 开启DataBinding(自动包含ViewBinding功能)
}
}
2.2.2 布局文件(需用<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.demo.User" />
</data>
<LinearLayout ...>
<!-- 直接绑定数据,无需手动setText -->
<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.power)}" />
</LinearLayout>
</layout>
2.2.3 数据类:
public class User extends BaseObservable { // 继承BaseObservable支持数据通知
private String name;
private int power;
@Bindable // 标记可绑定的字段
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name); // 通知UI更新
}
// power的get/set方法同理
}
2.2.4 Activity中使用:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
// 初始化数据
user = new User();
user.setName("如来");
user.setPower(9999);
// 绑定数据到布局
binding.setUser(user);
// 修改数据后,UI自动更新(无需手动调用binding.tvName.setText)
new Handler().postDelayed(() -> {
user.setName("狼狈躲闪的如来");
user.setPower(5000);
}, 2000);
}
}
3. 推荐使用策略(分场景)
🔥 优先用 ViewBinding 的情况(90%的日常开发)
- 项目以MVC/MVP为主,无自动数据绑定需求;
- 页面逻辑简单,仅需基础的View操作;
- 追求编译速度和代码简洁性;
- 团队成员以新手为主,降低维护成本。
📌 选择 DataBinding 的情况
- 项目采用MVVM架构(配合ViewModel/LiveData);
- 页面有大量实时更新的数据(如表单、动态列表);
- 希望减少“数据更新→View更新”的重复代码;
- 团队有足够经验处理数据绑定的复杂场景(如表达式、双向绑定)。
❌ 不推荐的用法
- 为了“尝鲜”在简单页面强行使用DataBinding(增加无意义的复杂度);
- 完全不用ViewBinding,仍依赖findViewById(易出空指针、类型错误)。
4. 总结
- 核心差异:ViewBinding仅解决View绑定,轻量高效;DataBinding在其基础上增加数据自动绑定,功能强但复杂度高;
- 选型原则:日常开发优先用ViewBinding,MVVM架构/大量数据更新场景用DataBinding;
- 核心建议:无论选哪种,都要替代findViewById,利用绑定类的空安全、类型安全特性提升代码稳定性。
Databingding和Compose
DataBinding 是用于传统View体系(XML布局)的绑定框架,而Compose是全新的声明式UI框架,二者本质上不冲突,可以在同一个项目甚至同一个页面中共存,但不能直接混用(比如在Compose中直接使用DataBinding绑定XML里的View)。
兼容性:DataBinding 和 Compose 不冲突,可在项目中共存,但作用域不同(DataBinding 针对 XML View,Compose 针对声明式 UI);
嵌入使用:在 Compose 中嵌入 DataBinding 的 View 需通过AndroidView,数据更新在update回调中处理;
优先原则:纯 Compose 页面优先使用 Compose 原生状态(mutableStateOf/StateFlow),而非 DataBinding,更符合 Compose 的设计理念。
你想知道DataBinding和Jetpack Compose是否存在冲突,以及在同时使用时该如何正确设置View相关的问题,这个疑问很常见,尤其是在项目从传统View体系向Compose迁移的过程中。
二者不冲突,但需正确区分使用场景,下面分两种核心场景列出具体的解决和使用方法:
场景1:项目中同时使用DataBinding(XML)和Compose
这种情况常见于“渐进式迁移”(比如部分页面用XML+DataBinding,部分用Compose),或在Compose页面中嵌入传统View(需用DataBinding绑定)。
1.1 基础配置(确保二者共存)
首先在build.gradle(Module级别)中同时开启DataBinding和Compose:
android {
...
buildFeatures {
// 开启DataBinding
dataBinding true
// 开启Compose
compose true
}
// Compose相关配置(根据你的Compose版本调整)
composeOptions {
kotlinCompilerExtensionVersion "1.5.3" // 需与Kotlin版本匹配
}
}
dependencies {
// Compose核心依赖(示例版本,可根据最新版调整)
implementation "androidx.compose.ui:ui:1.5.4"
implementation "androidx.compose.material:material:1.5.4"
implementation "androidx.activity:activity-compose:1.8.2"
// DataBinding无需额外依赖(开启buildFeatures即可)
}
1.2 在Compose中嵌入DataBinding的View
如果需要在Compose页面中使用传统XML布局(并通过DataBinding绑定数据),可以用AndroidView组件:
步骤1:编写XML布局(带DataBinding)
<!-- res/layout/layout_legacy.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- 定义绑定的变量 -->
<variable
name="userName"
type="String" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userName}" />
</layout>
步骤2:在Compose中嵌入并绑定数据
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.example.yourpackage.databinding.LayoutLegacyBinding
@Composable
fun ComposeWithDataBinding() {
val context = LocalContext.current
val userName = "Hello Compose + DataBinding"
Column {
// 用AndroidView嵌入传统View,并通过DataBinding绑定数据
AndroidView(
factory = { ctx ->
// 初始化DataBinding
val binding = LayoutLegacyBinding.inflate(ctx.layoutInflater)
// 绑定数据
binding.userName = userName
// 返回根View
binding.root
},
update = { binding ->
// 当数据更新时,重新绑定(比如userName变化时)
binding.userName = userName
}
)
// 其他Compose组件
// Text(text = "纯Compose组件")
}
}
1.3 关键注意事项
AndroidView的factory参数只执行一次(View创建时),数据更新要在update中处理;- 避免在Compose中直接操作DataBinding的View实例(比如
binding.textView.setText()),应通过DataBinding的变量绑定,符合双向绑定的设计; - Compose有自己的状态管理(
remember/MutableState),如果只是简单的数据展示,优先用Compose原生状态,而非DataBinding。
场景2:避免“错误混用”(核心避坑点)
很多新手会犯的错误是:试图在Compose函数中直接使用DataBinding绑定Compose组件,这是不可行的,因为Compose没有XML布局,自然无法用DataBinding的注解/XML变量绑定。
错误示例(不要这么做)
// 错误:Compose中无法使用DataBinding的@BindingAdapter等注解
@Composable
fun WrongUsage() {
// DataBinding的绑定逻辑对Compose组件无效
Text(text = "@{userName}") // 不会被解析,直接显示字符串"@{userName}"
}
正确替代方案(Compose原生状态管理)
Compose有自己的“数据绑定”方式(基于State/StateFlow),比DataBinding更轻量:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.Text
import androidx.compose.material3.Button
@Composable
fun ComposeStateBinding() {
// Compose原生状态(替代DataBinding的变量)
var userName by remember { mutableStateOf("初始名称") }
Column {
// 直接绑定状态,自动刷新UI
Text(text = userName)
// 点击按钮更新状态,UI自动刷新
Button(onClick = { userName = "更新后的名称" }) {
Text(text = "更新名称")
}
}
}