Jetpack Compose - 通过State理解与管理你的UI

1,527 阅读6分钟

由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。

本篇文章将从导图中五方面分别介绍State的相关概念和用法

简介

Compose是一种新式声明性界面工具包,它和我们之前一直接触的View体系完全不同,比如之前我们想更新TextView显示文字,可以直接使用TextView.setText()来改变界面上显示的文案,而在Compose中,已经不支持这种更新方式,取而代之的是更新这个可组合项引用的State值,这样就会触发可组合项的重组事件,重组之后可组合项会获取到State最新的值并显示在界面上,下方图片就是Compose中可组合项的生命周期:进入组合 -> 执行0次或多次重组 -> 最后是退出组合。

Compose中采用的是State来作用于可组合项中值的读取和更改,在值更改(与更改前值不同)之后就会触发可组合项的重组,从而达到界面刷新的机制。

remember{}

remember{}可以将对象存储在内存当中,在第一次组合的时候将从remember提供的值存储到组合项中,并在重组期间返回最新存储的值。

上面我们使用remember{ mutableStateOf("init") }创建了一个可变的State,然后在按钮点击之后改变它的值,最终会在Text中呈现出来,这里需要注意的一点就是通过= remember{}这种方式创建的结果是state对象,在引用或者修改其值时需要调用state.value,下面还有介绍两种不同的方式来创建state,原理都是一致只是返回的对象不同而已。

接着我们再来看看另外两种方式是如何使用state

  • by remember{}通过委托方式进行创建state,此方式返回的对象为state的泛型值
  • (value, changeValue) = remember{}通过解构方式进行创建state,此方式第一个参数为返回的具体值,第二个参数为Lambda表达式,可直接修改状态值

这里也有一点需要大家注意,就是第二种委托方式创建的对象如果需要进行修改,就得使用var来修饰,并且需要额外导入两个包androidx.compose.runtime.getValueandroidx.compose.runtime.setValue,否则AS会一直爆红提示。

通过上面的代码示例想必大家都已经了解了如何去使用remember{}方式去创建和使用state,到这为止大家不妨将手机屏幕旋转一下,看看更新后的state值是什么样,在小节开头的时候提到,remember{}方式是将值存到内存当中,它并不能在配置更改后依然保存最新的值,而是会恢复成初始值,如果遇到这种情况,大家就需要使用另外一种,那就是rememberSaveable{}它会将值保存到Bundle中,即使在屏幕旋转等配置更改之后也会取到最新的值,并不会丢失。具体用法这里就不再多做演示,大家可以自行体验一下。

LiveData.obseverAsState()

LiveData我们接触的比较早了,之前也写过一篇文章简单介绍了它的使用和源码,它是一种可观察的数据存储器类,通常结合ViewModel使用,下面我们就通过代码来看下在Compose种如何使用它来转换成State

使用LiveData.observeAsState()前需要添加额外的依赖:androidx.compose.runtime:runtime-livedata:1.4.3,它可以之前将LiveData转换成State,这样我们在引用LiveData值的时候就可以直接引用State的值了,修改值依然可以直接修改LiveData的值,Compose会自动响应修改的变化。

其内部实现也是非常简单,采用DisposableEffect副作用机制,内部添加LiveData的观察者,将观察到的值赋予state,然后在onDispose{}种取消观察者,通过源码可以看出,这里是绑定了生命周期的,在onDestroy()时会触发onDispose{},所以在使用这种方式的情况下,大家不必担心会产生内存泄露问题,内部已经帮我们处理过了,下面是源码大家可以自行消化下。

Flow.collectAsState()

Flow是Kotlin种一种数据流模型,它的原理就类似水流管道,上游发射下游接收,关于Flow也是写过两片文章分别介绍了基本的使用操作符大全,如果还没有接触过的小伙伴可以先看下前面两片文章,熟悉下如何简单使用,下面我们直接来看下在Compose种如何使用Flow

图片代码中我们采用的是collectAsState()Flow转换成State对象,读取其值是和LiveData一样,直接读取State的值,修改值时直接更新Flow的值。

这里的collectAsState()不推荐大家使用,因为它没有绑定生命周期,它内部是直接collect{}收集流的值,然后转换成State对象,并没有考虑到生命周期的问题,官方提供了另外一种方式帮助我们将Flow转换成State,也就是collectAsStateWithLifecycle(),这种方式采用了lifecycle.repeatOnLifecycle(){}来绑定生命周期,并且我们可以自定义在哪一时刻开始收集数据。

此方法有四个参数:

  • initalValue可以定义初始值
  • lifecycle用于管理生命周期,在Compose种可以通过LocalLifecycleOwner.current获取到当前的生命周期管理者
  • minActiveState此参数就是定义最小在哪个周期开始收集流的值
  • context这个就是协程的上下文,用于启动协程,默认为EmptyCoroutineContext

内部采用的是produceState()方式,这里具体用法会在文章接下来详细介绍,它就是将我们所需要的观察状态转换成State对象,这样就可以供Compose响应此状态。

produceState

produceState是Compose提供的一种创建State的方式,它可以帮助我们自由的生成State对象

这是最简单的一种方式,只有两个参数:

  • initialValue提供一个初始值
  • producer()它是一个挂起函数,我们可以在其内部使用协程并且提供State的值

下面我们通过此方式来模拟一个定时器的实现,倒计时60s,每秒更新下Text文字

实现过程也是非常的简单,在producer中通过协程的delay模拟延时1s的效果,然后通过this.value来更新State的值。具体效果见下方GIF

到这4种方式就介绍完了,除了produceState之外其它的三种基本上和我们项目的选型相关,如果项目已经采用了Kotlin的协程,那我们可以直接采用Flow的方式,目前已经完善的挺全面了,而且操作符也是应有尽有,大家可以按需选择~

关于我

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢~