news 2026/10/28 1:21:39

【Android】Compose中的状态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Android】Compose中的状态

一. Compose中的状态简介

“组合”是对 Compose 在执行可组合项时所构建界面的描述。Compose 应用调用可组合函数,以将数据转换为界面。如果发生状态更改,Compose 会使用新状态重新执行受影响的可组合函数,从而创建更新后的界面。这一过程称为“重组”。Compose 会为您安排重组。

当 Compose 首次运行可组合函数时,在初始组合期间,它会跟踪您为了描述组合中的界面而调用的可组合函数。重组是指 Compose 重新执行可能因数据更改而更改的可组合项,然后更新组合以反映所有更改

组合只能通过初始组合生成且只能通过重组进行更新。重组是修改组合的唯一方式。为此,Compose 需要知道要跟踪的状态,以便在收到更新时能够安排重组

在Compose中,您可以使用State和MutableState类型让应用中的状态可被Compose 观察或跟踪。State类型不可变,因此您只能读取其中的值,而MutableState类型是可变的。您可以使用mutableStateOf()函数来创建可观察的MutableState。它接受初始值作为封装在 State 对象中的形参,这样便可使其 value 变为可观察。

mutableStateOf()函数返回的值:

  • 会保持状态
  • 可变,因此值可以修改
  • 可观察,因此 Compose 会观察值的所有更改并触发重组以更新界面。

二. 组合的状态

2.1 MutableState(可变状态)

下面我们通过一个TextFiled内容改变来探索下状态变化

示例代码:

@ComposablefunEditNumberField(modifier:Modifier=Modifier){varamountInput="0"TextField(value=amountInput,onValueChange={},modifier=modifier)}
  1. 使用MutableState<String>类型(而非硬编码的String变量),以便 Compose 知道要跟踪amountInput状态,然后传入 “0” 字符串,该字符串是amountInput状态变量的初始默认值
varamountInput:MutableState<String>=mutableStateOf("0")// 也可以使用类型推断编写 amountInput 初始化,如下所示:varamountInput=mutableStateOf("0")

mutableStateOf() 函数接受初始值 “0” 作为实参,这样便可使其 amountInput 变为可观察

  1. 在TextFiled组合中使用amountInput.value属性
TextField(value=amountInput.value,onValueChange={},modifier=modifier)

Compose 会跟踪每个读取状态value属性的可组合项,并在其value更改时触发重组。

当文本框的输入更改时,系统会触发onValueChange回调。在 lambda 表达式中,it变量包含新值。

@ComposablefunEditNumberField(modifier:Modifier=Modifier){varamountInput=mutableStateOf("0")TextField(value=amountInput.value,onValueChange={amountInput.value=it},modifier=modifier)}

当TextField通过onValueChange回调函数通知您文本发生更改时,您将更新TextField的状态(即amountInput变量)。

当你运行程序时发现, 不管你怎么输入,输入框的内容总是保持0不变。

原因如下:

用户在文本框中输入文本后,系统会调用onValueChange回调,并使用新值更新amountInput变量。Compose 跟踪amountInput状态,因此当其值更改时,系统会安排重组并再次执行EditNumberField()可组合函数。在该可组合函数中,amountInput变量会重置为初始 0 值。因此,文本框会显示 0 值。

2.2. remember(记住状态)

可组合方法可能会因重组而被系统多次调用。如果不保存,可组合项就会在重组期间重置状态。

可组合函数可以使用remember跨重组存储对象。初始组合期间,remember函数计算的值会存储在组合中,而存储的值会在重组期间返回。remember和mutableStateOf函数通常在可组合函数中一起使用,以使状态及其更新正确反映在界面中

// 使用 remember 将对 mutableStateOf() 的调用括起来,以便使用 by remember Kotlin 属性委托来初始化 amountInput 变量。amountInput 属性的默认 getter 和 setter 函数分别委托给 remember 类的 getter 和 setter 函数。// by 是Kotlin的属性委托语法// var amountInput by remember { mutableStateOf("") } 效果是把mutableStateOf的 .value读写操作委托给amountInput变量// 这样你就可以直接使用amountInput代替amountInput.value// var amountInput = remember { mutableStateOf("") }// 修改值: amountInput.value = "测试"// 读取值: println(amountInput.value)varamountInputbyremember{mutableStateOf("")}

更新后的代码如下:

@ComposablefunEditNumberField(modifier:Modifier=Modifier){// 初始化常量金额varamountInputbyremember{mutableStateOf("")}TextField(value=amountInput,onValueChange={amountInput=it},modifier=modifier)}

注意:在初始组合期间,TextField 中的 value 会设置为初始值,该值为一个空字符串。
当用户在文本字段中输入文本时,系统会调用 onValueChange lambda 回调,执行 lambda,并将 amountInput.value 设置为在文本字段中输入的更新后的值。
amountInput 是 Compose 跟踪的可变状态,系统会安排重组。EditNumberField() 可组合函数会重组。由于您使用的是 remember { }, 因此相应更改在重组后会保持不变,这就是状态不会重新初始化为 “” 的原因。
文本字段的 value 已设置为记住的 amountInput 值。文本字段会重组(使用新值在屏幕上重新绘制)。

2.3. 状态提升

在可组合函数中,您可以定义一些变量,用于保存要在界面中显示的状态。例如,您在EditNumberField()可组合项中将amountInput变量定义为状态。

当您的应用变得越来越复杂并且其他可组合项需要访问 EditNumberField() 可组合项中的状态时,您需要考虑将 EditNumberField() 可组合函数中的状态提升或提取出来。

当您需要执行以下操作时,应该提升状态:

  • 与多个可组合函数共享状态。
  • 创建可在应用中重复使用的无状态可组合项

在您从可组合函数中提取状态后,生成的可组合函数称为无状态函数。也就是说,通过从可组合函数中提取状态,可以将其变为无状态。

无状态可组合项是指没有状态的可组合项,这意味着它不会保存、定义或修改新状态。相反,有状态可组合项是指具有可以随时间变化的状态的可组合项。

提升状态后的代码:

@ComposablefunTipTimeLayout(){// 初始化常量金额varamountInputbyremember{mutableStateOf("")}// 计算小费valamount=amountInput.toDoubleOrNull()?:0.0valtip=calculateTip(amount)Column(modifier=Modifier.statusBarsPadding().padding(horizontal=40.dp).safeDrawingPadding(),horizontalAlignment=Alignment.CenterHorizontally,verticalArrangement=Arrangement.Center){Text(text=stringResource(R.string.calculate_tip),modifier=Modifier.padding(bottom=16.dp,top=40.dp).align(alignment=Alignment.Start))// 文本输入框EditNumberField(value=amountInput,onValueChange={amountInput=it},modifier=Modifier.padding(bottom=32.dp).fillMaxWidth())Text(text=stringResource(R.string.tip_amount,tip),style=MaterialTheme.typography.displaySmall)Spacer(modifier=Modifier.height(150.dp))}}// 把该方法内的状态提升到上一级方法, 然后通过参数和回调方法来 更新显示状态@ComposablefunEditNumberField(value:String,onValueChange:(String)->Unit,modifier:Modifier=Modifier){TextField(label={Text(stringResource(R.string.bill_amount))},// 添加文本标签singleLine=true,// 将文本框从多行压缩成可水平滚动的单行keyboardOptions=KeyboardOptions(keyboardType=KeyboardType.Number),// 指定键盘类型value=value,onValueChange=onValueChange,modifier=modifier)}

2.4. 代码示例最终代码

classMainActivity:ComponentActivity(){overridefunonCreate(savedInstanceState:Bundle?){enableEdgeToEdge()super.onCreate(savedInstanceState)setContent{TipTimeTheme{Surface(modifier=Modifier.fillMaxSize(),){TipTimeLayout()}}}}}@ComposablefunTipTimeLayout(){// 初始化常量金额varamountInputbyremember{mutableStateOf("")}vartipInputbyremember{mutableStateOf("")}varroundUpbyremember{mutableStateOf(false)}// 计算小费valamount=amountInput.toDoubleOrNull()?:0.0valtipPercent=tipInput.toDoubleOrNull()?:15.0valtip=calculateTip(amount,tipPercent,roundUp)Column(modifier=Modifier.statusBarsPadding().padding(horizontal=40.dp).safeDrawingPadding().verticalScroll(rememberScrollState()),//向修饰符添加 .verticalScroll(rememberScrollState()),使列能够垂直滚动。rememberScrollState() 会创建并自动记住滚动状态。horizontalAlignment=Alignment.CenterHorizontally,verticalArrangement=Arrangement.Center,){Text(text=stringResource(R.string.calculate_tip),modifier=Modifier.padding(bottom=16.dp,top=40.dp).align(alignment=Alignment.Start))// 文本输入框 (金额)EditNumberField(R.string.bill_amount,leadingIcon=R.drawable.money,value=amountInput,onValueChange={amountInput=it},keyboardOptions=KeyboardOptions(keyboardType=KeyboardType.Number,imeAction=ImeAction.Next),modifier=Modifier.padding(bottom=32.dp).fillMaxWidth())// 小费 文本输入框EditNumberField(R.string.how_was_the_service,leadingIcon=R.drawable.percent,value=tipInput,onValueChange={tipInput=it},keyboardOptions=KeyboardOptions(keyboardType=KeyboardType.Number,imeAction=ImeAction.Done),modifier=Modifier.padding(bottom=32.dp).fillMaxWidth())// 是否小费取整的UIRoundTheTipRow(roundUp,onCheckedChange={roundUp=it},Modifier.padding(bottom=32.dp))Text(text=stringResource(R.string.tip_amount,tip),style=MaterialTheme.typography.displaySmall)Spacer(modifier=Modifier.height(150.dp))}}@ComposablefunEditNumberField(@StringReslabel:Int,// 为该参数添加注解, 表示 label 形参应为字符串资源引用, @StringRes 注解是一种类型安全的字符串资源使用方法,用于表明要传递的整数是 values/strings.xml 文件中的字符串资源。@DrawableResleadingIcon:Int,value:String,onValueChange:(String)->Unit,keyboardOptions:KeyboardOptions,modifier:Modifier=Modifier){TextField(label={Text(stringResource(label))},// 添加文本标签leadingIcon={Icon(painter=painterResource(leadingIcon),null)},// 添加左侧的icon图标singleLine=true,// 将文本框从多行压缩成可水平滚动的单行keyboardOptions=keyboardOptions,// 指定键盘类型value=value,onValueChange=onValueChange,modifier=modifier)}@ComposablefunRoundTheTipRow(roundUp:Boolean,onCheckedChange:(Boolean)->Unit,modifier:Modifier=Modifier){Row(modifier=modifier.fillMaxWidth().height(48.dp),verticalAlignment=Alignment.CenterVertically,horizontalArrangement=Arrangement.SpaceBetween){Text(stringResource(R.string.round_up_tip))Switch(checked=roundUp,onCheckedChange=onCheckedChange,)}}/** * Calculates the tip based on the user input and format the tip amount * according to the local currency. * Example would be "$10.00". */privatefuncalculateTip(amount:Double,tipPercent:Double=15.0,roundUp:Boolean):String{vartip=tipPercent/100*amountif(roundUp){tip=kotlin.math.ceil(tip)// 向上取整函数}returnNumberFormat.getCurrencyInstance().format(tip)}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 1:21:24

Coze工作流导入避坑指南:依赖扫描与调试实战

简介&#xff1a;这份资源是面向自媒体创作者与内容运营者的Coze工作流模板合集&#xff0c;针对内容生产流程零散、发布节奏难以把控等痛点&#xff0c;提供可直接参考或二次调整的流程方案&#xff0c;适合希望用工具提升效率的初、中级自媒体人。压缩包共5个文件&#xff0c…

作者头像 李华
网站建设 2026/10/28 1:20:02

KOReader DRM 电子书处理:4 步解锁流程 + 排障速查表

KOReader DRM 电子书处理&#xff1a;4 步解锁流程 排障速查表 【免费下载链接】koreader An ebook reader application supporting PDF, DjVu, EPUB, FB2 and many more formats, running on Cervantes, Kindle, Kobo, PocketBook and Android devices 项目地址: https://g…

作者头像 李华
网站建设 2026/10/28 1:18:23

01:统计数字字符个数

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:16:16

itunes登录协议分析

iTunes X64位, 版本12.6.5.3 调试工具:X64dbg、IDAPro 、HTTPdebugger 首先打开X64dbg,附加itunes. 发现程序直接退出,有反调试的,不过只是应用层的。 这里可以直接使用反反调试插件,但出于好奇还是看了看。 1.过反调试 直接下结束进程断点,kernel32.dll 的ExitProces…

作者头像 李华
网站建设 2026/10/28 1:15:37

【GATT】规范精讲[8]: GATT 的地下管道——L2CAP 承载与信道修路人

GATT系列前七篇把 GATT 过程部分讲了个遍:发现、读、写、通知、指示,全在 ATT 层打转转。但 ATT PDU 本质上只是一包包字节,它们要穿过链路到达对端,离不开脚下那条 L2CAP 通道。你可以把 GATT 想象成城市里的公交系统,前面研究的 MTU 是单车厢容量,opcode 是线路编号,而…

作者头像 李华
网站建设 2026/10/28 1:15:02

【LLC 全波整流电路设计】

LLC 基本特性 LLC 电路的优点&#xff1a; 1 &#xff0c; 原边软开关&#xff0c;减少开通损耗&#xff0c;对于高压输入应用&#xff0c;有很大的优势 原边MOSFET 开通损耗的分析&#xff0c;特别在高压应用情况下&#xff0c;这部分损耗非 常可观 2 &#xff0c;副边的整流 …

作者头像 李华