Compose 状态与组合

摘要

本文主要介绍的是,在Compose实际编程中,当数据发生变更后,怎么去刷新界面。Compose通过可观察的状态,来触发组合的重组。将状态的显示与状态的存储和更改解耦,通过观察者模式来驱动界面变化。

Android的视图层次结构中,界面是通过一个个的View,例如LinearLayoutTextView等搭建而成,我们对其的修改,都是通过findViewById找到该View的引用后设置它的内部状态,例如设置TextView的文本。而Jetpack Compose是一个适用于Android的新式声明性工具包,那么组合与状态解耦之后,它们如何协作。或者简单说,在Compose中,我如何去更新Text的文本?

三个概念

学习Compose,我想应该要明白三个概念,即组合状态事件。组合是用于描述界面,通过可运行组合项生成。组合是描述界面的可组合项的树结构。应用中的状态是指可以随时间变化的任何值。例如动画,网络请求后的数据。事件由用户或程序产生,例如点击事件。在Compose中,状态与事件分开,状态表示可更改的值,而事件则表示有情况发生的通知。而将状态与事件分开,可以将状态的显示与状态的存储和更改方式解耦。

1
2
3
4
5
6
7
val count= remember{ mutableStateOf(0) }

Column() {
Button(onClick = { count.value++ }) {
}
Text(text = "${count.value}")
}

上述代码中,将点击数状态与点击事件解耦后,点击数就可以单独拎出来,同时Text文本的点击数状态显示与其更改方式也解耦了。

remeber可以用来存储单个对象,系统会在初始期存储由remember计算的值,并在重组时返回所存储的值。存储对象可以是可变对象,也可以是不可变对象。

状态存储与更改

通过remember存储可变对象时,会向可组合项添加内部状态。例如上一小节中就给Text设置一个单独的状态。mutableStateOf函数会创建可观察的MutableState<T>对象,是Compose运行时集成的可观察类型,同时还有它的父接口State<T>。Compose建议将状态设置为可观察的,这样当状态发生更改时,Compose可以自动重组界面。

除了``MutableState来存储状态,我们也可以使用我们熟悉的LiveData,Flow,RxJava`。

1
2
val liveData= MutableLiveData<String>()
val text by liveData.observeAsState()

observeAsState函数是LiveData的扩展函数,将LiveData对象转化成State对象。

Flow

1
val value: String by stateFlow.collectAsState()

RxJava

1
val completed by completable.subscribeAsState()

Compose通过扩展函数,将已有框架的可观察类转化成State<T>对象,并由可组合函数读取,因此,我们可以通过扩展函数将自己的可观察类转化成State<T>对象。

如果需要手动触发重组,例如在从服务器获取数据的情况下,通过使用currentRecomposeScope.invalidate()

状态的提升

状态的提升指的是一种将状态移至可组合项的调用方以使可组合项无状态的模式。一来调用方可以通过状态修改组合项,而不止是组合项自身调用。二来不同组合项可以复用同个状态。

状态提升一般是将状态变量替换成两个参数:

  • value: T:要显示的当前值
  • onValueChange: (T) -> Unit:请求更改值的事件,其中 T 是建议的新值。

重组

在初始组合期间,Jetpack Compose 将跟踪您为了描述组合中的界面而调用的可组合项。然后,当应用的状态发生变化时,Jetpack Compose 会安排重组。重组过程中会运行可能已更改的可组合项以响应状态变化,Jetpack Compose 会更新组合以反映所有更改。

通过重组来刷新界面树中状态已经发生变化的微件。重组是智能的,系统会根据需要使用新数据重新绘制(组合函数发出的)微件,而不依赖新数据的(组合函数发出的)微件则不会被重绘。

在Compose编程中,也要注意到以下几点:

  • 可组合函数可以按任何顺序执行。意味着程序顺序一致性在可组合函数中是不存在,所以不能在Compose可组合函数设置某个全局变量,而是在它们调用方。

    1
    2
    3
    4
    5
    Column() {
    Text(text = "我是Text1")
    Text(text = "我是Text2")
    Text(text = "我是Text3")
    }

    例如这里的三个Text,并不是一定先调用了Text1,再调用Text2,再到Text3函数,它们可能是乱序调用。但它们最终的组合顺序还是被Column布局一样,分别从上到下。

  • 可组合函数可以并发执行。意味着Compose可以利用CPU多核心功能,通过并行运行可组合函数来优化重组。

  • 重组会跳过尽可能多的内容。Compose会尽力只重组需要更新的部分,也就是会跳过尽可能多的可组合项。每个可组合函数和lambda都可以自行重组。

  • 可组合函数可能会非常频繁运行

    在某些情况下,可能对针对界面动画的每一帧运行一个可组合函数。如果在改组合函数执行成本高昂的操作(例如网络请求,从文件读取数据),可能会造成界面卡顿。所以应该将成本高昂的操作迁移到其他线程,在通过ViewModelLiveData将数据传递给可组合函数。

最后

本文来自官方文档的状态与组合资料的学习,同时由于版本的升级,一些信息已经变更,需要参考文档更新记录

欢迎点赞+关注+评论三连击

Github】【掘金】【博客