21人参与 • 2025-11-26 • Android
在 jetpack compose 中构建界面,核心逻辑围绕“状态控制”展开,因为 compose 的界面本身是不可变的 : 一旦绘制完成,就没法直接修改。我们能操控的只有界面的状态,只要状态发生变化,compose 就会智能地重新创建界面树中那些已经改变的部分。像 textfield 这样的可组合项,就是典型的“接收状态、公开事件”的组件:它接收一个值来显示文本,同时通过 onvaluechange 回调这种事件,来处理用户修改文本的操作。比如下面这段常见代码:
var name by remember { mutablestateof("")}
outlinedtextfield(
value = name,
onvaluechange = { name = it },
label ={ text ("name" )}
)由于可组合项天生具备“接收状态、公开事件”的特性,单向数据流模式和 jetpack compose 可以说是完美适配。这篇指南就专门讲怎么在 compose 里用单向数据流模式、怎么实现事件和状态容器,还有如何结合 viewmodel 使用。
注意:用 jetpack compose 开发界面,并不会影响应用的其他层,比如负责数据存储的数据层和负责业务逻辑的业务层。如果想了解整个应用所有层级的构建方法,可以参考官方的应用架构指南。
单向数据流(udf)是一种简单清晰的设计模式,核心规则就两条:状态向下流动,事件向上传递。采用这种模式后,负责显示状态的可组合项,和负责存储、修改状态的应用部分就能彻底分离开,各司其职。
uistate 状态(包含登录状态、用户昵称等数据),这个状态会向下传递给登录界面、个人中心界面等可组合项;isbuttonenabled 状态,向下传给子可组合项 button,用于控制按钮是否可点击;uistate 中的昵称显示文本,根据 isbuttonenabled 决定按钮的交互状态。button 组件内部处理,而是通过 onclick 回调向上传递给 viewmodel,由 viewmodel 执行登录请求、校验账号密码等业务逻辑;textfield 输入文本,通过 onvaluechange 回调将输入事件向上传递,由上层组件(或 viewmodel)更新对应的文本状态;这就是单向数据流界面更新的三步完整循环,逻辑一目了然 :
在 jetpack compose 中使用单向数据流,能解决开发中的不少麻烦,带来三个核心好处:
stateflow 或者 livedata 这类可观察的状态容器,任何状态更新都会立刻反映在界面上,不会出现“状态变了,界面没更”的情况。可组合项的工作核心就是“状态”和“事件”。比如 textfield,只有当它的 value 参数更新,并且通过 onvaluechange 回调(本质是请求改值的事件)触发时,它才会更新显示内容。
在 compose 里,state 对象是专门用来存值的容器,而且它有个关键特性:状态值一旦改变,就会触发读取该值的可组合函数重组。我们存储状态有两种常用方式,选哪种取决于需要保留状态的时长:
remember { mutablestateof(value) }remembersaveable { mutablestateof(value) }textfield 的值是 string 类型,这个值的来源很灵活,可以是硬编码的固定值、viewmodel 中的数据,也可以是从父级可组合项传过来的。虽然不一定非要把它存在 state 对象里,但必须注意:当 onvaluechange 事件触发时,一定要手动更新这个值。
mutablestate 对象,这是 compose 里的可观察类型。只要它的值变了,系统就会安排所有读取过这个值的可组合函数进行重组。remember 的可组合项从组合中被移除了,那它存储的这个对象也会被忘记。remember 多了个“持久化”能力——它会把状态存在 bundle 里,就算应用遇到屏幕旋转这种配置更改,状态也不会丢失。注意:如果想深入了解 compose 中的状态以及状态提升的相关知识,可以参考专门的“状态和 jetpack compose”指南。
给可组合项定义状态参数时,别盲目添加,先想清楚两个问题:这个可组合项需要有多高的可重用性和灵活性?这些状态参数会对它的性能产生什么影响?
核心原则是:为了方便分离逻辑和重复使用,每个可组合项只包含最必要的信息。举个例子,我们要做一个显示新闻标题的可组合项 header,两种写法差别很大:
@composable
fun header(title: string, subtitle: string) {
// 只有 title 或 subtitle 变化时,才会重组
}
@composable
fun header(news: news) {
// 只要传入新的 news 实例,不管标题副标题变没变化,都会重组
}有时候用独立的参数还能提升性能。比如 news 类里除了标题和副标题,还有作者、时间等很多其他信息,这时用 header(news) 的写法就很不划算——哪怕只有无关信息变了,生成了新的 news 实例,header 也会无辜重组。
另外还要注意参数数量:如果一个可组合函数的参数太多,使用起来会很麻烦。这种情况下,建议把这些参数整合到一个类里,再传递这个类的对象。
应用里所有用户输入或系统通知,都应该被当作事件来处理——比如按钮点击、文本输入变化,甚至是计时器触发、网络请求结果通知等。这些事件触发后,不能由界面层直接修改状态,而是要交给 viewmodel 处理,再由 viewmodel 去更新界面状态。
这里有个重要原则:界面层绝对不能在事件处理逻辑之外修改状态,不然很容易导致应用状态混乱,出现各种难以排查的 bug。
给状态和事件处理的 lambda 传递参数时,最好用不可变值。这么做有四个明显好处:
比如应用的顶部导航栏(topappbar),通常都要显示文本,还要有个返回按钮。我们可以做一个通用的 myapptopappbar 可组合项,专门接收文本内容和返回按钮的点击事件,这样在整个应用里都能复用:
@composable
fun myapptopappbar(topappbartext: string, onbackpressed: () -> unit) {
topappbar(
title = {
text(
text = topappbartext,
textalign = textalign.center,
modifier = modifier.fillmaxsize().wrapcontentsize(alignment.center)
)
},
navigationicon = {
iconbutton(onclick = onbackpressed) {
icon(
icons.filled.arrowback,
contentdescription = localizedstring
)
}
},
// ...
)
}结合 viewmodel 和 mutablestateof,我们就能在应用中完整实现单向数据流。核心要求有两个:一是界面状态要通过 stateflow 或 livedata 这类可观察的状态容器公开;二是 viewmodel 要负责处理来自界面或其他层的事件,并根据事件更新状态容器。
我们以登录屏幕为例,看看具体怎么实现。一个登录屏幕通常有四种状态,而且状态之间是互斥的,用密封类(密封类能限制状态的取值范围,很适合这种场景)建模最合适:
viewmodel 里会维护一个私有状态 _uistate,再对外提供一个只读的 uistate 供界面访问。同时,viewmodel 还会提供 onsignin() 这样的方法,专门处理登录事件。代码示例如下:
class myviewmodel : viewmodel() {
private val _uistate = mutablestateof<uistate>(uistate.signedout)
val uistate: state<uistate> get() = _uistate
// ...
}除了 mutablestateof,compose 还为 livedata、flow、observable 这些常用组件提供了扩展,让它们能注册为监听器,把自身的值当作 compose 状态来使用。比如用 livedata 的写法:
class myviewmodel : viewmodel() {
private val _uistate = mutablelivedata<uistate>(uistate.signedout)
val uistate: livedata<uistate> get() = _uistate
// ...
}
@composable
fun mycomposable(viewmodel: myviewmodel) {
val uistate = viewmodel.uistate.observeasstate()
// ...
}到此这篇关于android compose 界面架构 : 基于单向数据流的文章就介绍到这了,更多相关android compose 单向数据流内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论