如何在Jetpack Compose中实现自定义MutableState<T>容器,支持可变类型观测
Jetpack Compose自定义可观测状态容器实现方案
Compose的状态观测核心依赖公开的Snapshot快照体系,不需要调用内部未公开的Java类即可实现自定义类型的可观测能力,核心逻辑是:在自定义容器中封装可变实例,用一个基础状态作为变更通知触发器,所有修改操作完成后触发触发器变更,所有读取操作先订阅触发器状态,即可让Compose感知到可变类型的变更。
通用实现思路
- 内部持有目标可变类型的真实实例,同时声明一个
mutableStateOf类型的变更触发器,不需要存储实际业务值,仅作为通知信号使用 - 重写所有会修改真实实例的方法,完成修改操作后更新触发器的值,通知快照系统发生变更
- 对外暴露实例读取入口,读取时先读取触发器的值,让快照系统将当前重组作用域订阅到该状态的变更
可观测Path实现示例
class SnapshotPath(private val path: Path = Path()) : Path by path { // 变更通知触发器,仅做信号使用 private val changeSignal = mutableStateOf(0) // 重写所有修改Path的方法,操作完成后触发变更 override fun moveTo(x: Float, y: Float) { path.moveTo(x, y) changeSignal.value++ } override fun lineTo(x: Float, y: Float) { path.lineTo(x, y) changeSignal.value++ } override fun quadTo(x1: Float, y1: Float, x2: Float, y2: Float) { path.quadTo(x1, y1, x2, y2) changeSignal.value++ } override fun reset() { path.reset() changeSignal.value++ } // 其余Path的修改类方法全部按照上述逻辑重写即可 // 对外提供Path实例读取入口,读取时自动订阅状态变更 fun observe(): Path { changeSignal.value return this } }
使用示例:
@Composable fun DrawPathDemo() { val customPath = remember { SnapshotPath() } Canvas(modifier = Modifier.fillMaxSize()) { // 读取Path时调用observe()方法,订阅变更 drawPath(customPath.observe(), color = Color.Blue, style = Stroke(width = 2.dp.toPx())) } Button(onClick = { // 修改Path自动触发Canvas重组 customPath.lineTo(Random.nextFloat() * size.width, Random.nextFloat() * size.height) }) { Text("添加路径节点") } }
通用可变对象可观测包装方案
针对自定义可变对象、MutableList等通用可变类型,可以实现通用包装类,避免重复编码:
class SnapshotMutableWrapper<T>(private val value: T) { private val changeSignal = mutableStateOf(0) // 读取可变实例,自动订阅变更 fun get(): T { changeSignal.value return value } // 批量修改可变实例,完成后自动通知变更 fun update(block: T.() -> Unit) { value.block() changeSignal.value++ } } // 提供类似官方API的创建方法 fun <T> mutableSnapshotStateOf(value: T) = SnapshotMutableWrapper(value)
使用示例(包装MutableList):
@Composable fun ListDemo() { val list = remember { mutableSnapshotStateOf(mutableListOf("a", "b", "c")) } Column { // 读取列表自动订阅变更 list.get().forEach { Text(it) } Button(onClick = { // 修改列表自动触发重组 list.update { add(Random.nextLong().toString()) } }) { Text("添加列表项") } } }
注意事项
- 上述实现为全量观测,只要发生修改就会触发所有订阅该状态的重组,绝大多数业务场景下性能足够;如果需要类似官方
mutableStateListOf的细粒度观测能力,可以内部维护多个触发器或者使用SnapshotStateMap等官方提供的细粒度状态容器做映射,仅在对应维度修改时触发对应触发器即可 - 必须保证所有修改操作都会更新触发器,所有读取操作都会先读取触发器,否则会出现Compose感知不到变更、或者订阅失效的问题
内容的提问来源于stack exchange,提问作者Richard Onslow Roper
相关产品推荐
相关产品推荐

