我们知道Jetpack Compose(以下简称Compose)中的 UI 可组合项是通过@Composable 声明的函数来描述的,如:
@Composable
fun Greeting() {
    Text(
        text = "init",
        color = Color.Red,
        modifier = Modifier.fillMaxWidth()
     )
}
上面的代码描述的是一个静态的 Text,那么如何让 Compose 中的UI更新呢?
状态和重组
Compose 更新UI的唯一方法是通过新参数调用同一可组合项。可组合项中的状态更新时,就会发生重组。
State状态
mutableStateOf() 会创建可观察的 MutableState<T>,如下:
@Stable
interface MutableState<T> : State<T> {
    override var value: T
}
当value有任何变化时,Compose 会自动为读取 value 的所有可组合函数安排重组。但是靠State只能完成重组,并不能完成UI更新,说的有点绕,直接来看示例:
@Composable
fun Greeting() {
    val state = mutableStateOf("init")
    log("state:${state.value}")//Logcat
       
    Column {
       Text(
           text = state.value,
           color = Color.Red,
           modifier = Modifier.fillMaxWidth()
        )
       Button(onClick = { state.value = "Jetpack Compose" }) {
          Text(text = "点击更改文本")
        }
     }
}

多次点击按钮,执行结果如下:
14:25:34.493  E  state:init
14:25:35.919  E  state:init
14:25:37.365  E  state:init
......
可以看到点击Button按钮后确实执行重组了,但是Text中的文本并没有相应更新!这是因为每次进行重组时,可组合项Greeting() 中的 state 又被重新初始化了,导致UI并没有更新。能不能在下次进行重组时保存State<T>中的value值呢,答案是肯定的!可以结合 remember 来使用。
remember
Compose 会在初始组合期间将由 remember 计算的值存储在组合内存中,并在重组期间返回存储的值。remember 既可用于存储可变对象,又可用于存储不可变对象。我们将上面的代码修改如下:
@Composable
fun Greeting() {
  //前面加了remember,其他都不变
  val state = remember { mutableStateOf("init") }
  log("state:${state.value}")
  ......
}
点击 Button 按钮后:

执行结果:
15:06:04.544  E  state:init
//点击Button按钮后:
15:06:07.313  E  state:Jetpack Compose
可以看到UI 成功的更新了。
remember(key1 = resId) { } 控制对象缓存的生命周期
@Composable
inline fun <T> remember(
    key1: Any?,
    calculation: @DisallowComposableCalls () -> T
): T {
    return currentComposer.cache(currentComposer.changed(key1), calculation)
}
除了缓存 State 状态之外,还可以使用 remember 将初始化或计算成本高昂的对象或操作结果存储在组合中。
如上,remember 还可以接受key参数,当key发生变化,缓存值会失效并再次对 lambda 块进行计算。这种机制可控制组合中对象的生命周期。这样带来的好处是不会在每次重组时都进行对象重建高成本操作,如:
val bitmap = remember(key1 = resId) {
       ShaderBrush(BitmapShader(ImageBitmap.imageResource(res, resId).asAndroidBitmap(),
                  Shader.TileMode.REPEAT, Shader.TileMode.REPEAT
 ))}
上述代码即使发生在频繁重组的可组合项中,只要 key1 = resId 不变,那么ShaderBrush 就不会重新创建,从而提高了性能。
rememberSaveable 与自定义Saver
- remember 在重组后保持状态,但不会在配置更改后保持状态;
- 如果想在配置更改后保持状态,可以使用 rememberSaveable 代替;
- rememberSaveable 会自动保存可保存在 Bundle 中的任何值;如果不支持Bundle存储,可以将对象声明为 @Parcelize 可序列化,如果不能序列化,还可以将其传入自定义 Saver 对象。
示例:
//1、使用@Parcelize注解
//记得引入 apply plugin: 'kotlin-parcelize'插件
@Parcelize
data class CityParcel(val name: String, val country: String) : Parcelable
data class City(val name: String, val country: String)
//2、MapSaver自定义存储规则,将对象转换为系统可保存到 Bundle 的一组值。
val CityMapSaver = run {
    val nameKey = "Beijing"
    val countryKey = "China"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}
//3、ListSaver自定义存储规则
val CityListSaver = listSaver<City, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)
可组合项中使用它们:
@Composable
fun Greeting() {
// 1、如果涉及到配置更改后的状态恢复,直接使用rememberSaveable,会将值存储到Bundle中
var parcelCity by rememberSaveable {
      mutableStateOf(CityParcel("Beijing", "China"))
}
// 2、如果存储的值不支持Bundle,可以将Model声明为@Parcelable 或者使用MapSaver、ListSaver自定义存储规则
var mapSaverCity by rememberSaveable(stateSaver = CityMapSaver) {
      mutableStateOf(City("Beijing", "China"))
}
var listSaverCity by rememberSaveable(stateSaver = CityListSaver) {
      mutableStateOf(City("Beijing", "China"))
}
log("parcelCity: $parcelCity")
log("mapSaverCity: $mapSaverCity")
log("listSaverCity: $listSaverCity")
}
执行结果:
17:35:36.810  E  parcelCity: CityParcel(name=Beijing, country=China)
17:35:36.810  E  mapSaverCity: City(name=Beijing, country=China)
17:35:36.810  E  listSaverCity: City(name=Beijing, country=China)
State与 remember结合使用
一般Compose中 MutableState 都是需要跟 remember 组合使用(可乐配鸡翅,天生是一对~),在可组合项中声明 MutableState 对象的方法有三种:
val mutableState = remember { mutableStateOf("init0") } //1、返回MutableState<T>类型
var value1 by remember { mutableStateOf("init1") } //2、返回T类型
val (value2, setValue) = remember { mutableStateOf("init") } //3、返回两个值分别为:T,Function1<T, kotlin.Unit>
第二种的by委托机制是最常用的,不过需要导入:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
UI 接收重组数据的几种方式
现代 Android 架构不管是 MVVM 还是 MVI ,都会用到ViewModel,在ViewModel中通过LiveData、Flow去操作数据,并在UI 层监听数据变化,当数据变化时,UI 层根据监听到的新数据做UI刷新,也就是数据驱动。
Compose中的 UI 界面刷新思路是一样的,只不过需要将得到的数据进行一下转换而已:
- 对于 LiveData,需要将LiveData<T>转换为State<T>;
- 对于 Flow,需要将Flow<T>转换为State<T>。
记住必须将新数据转换为 State<T>格式,这样 Compose 才可以在状态发生变化后自动重组。
Flow.collectAsState() & Flow.collectAsStateWithLifecycle()如何选择
//ViewModel层
class ComposeVModel : ViewModel(){
    //StateFlow UI层通过该引用观察数据变化
    private val _wanFlow = MutableStateFlow<List<WanModel>>(ArrayList())
    val mWanFlow: StateFlow<List<WanModel>> = _wanFlow
    //请求数据
    fun getWanInfoByFlow(){
        ......
    }
}
//UI层
import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun Greeting(vm: ComposeVModel = viewModel()) { 
    //2、将 Flow<T> 转换成 State<T>
    val state by vm.mWanFlow.collectAsStateWithLifecycle()
    Column {
       Text(
          text = "$state",
          color = Color.Red,
          modifier = Modifier.fillMaxWidth()
        )
    //1、点击通过ViewModel请求数据
    Button(onClick = { vm.getWanInfoByFlow() }) {
        Text(text = "点击更改文本")
      }
   }
}
上述代码1处通过Button点击进行网络请求,2处负责将 Flow<T> 转换成 State<T>,当数据有更新时,可组合项就可以进行重组,这样整个流程就串起来了。在Android 项目中,collectAsState() 与 collectAsStateWithLifecycle() 该选择哪个使用呢?
1、collectAsStateWithLifecycle() 会以生命周期感知型方式从 Flow 收集值。它通过 Compose State 表示最新发出的值,在 Android 开发中请使用这个方法来收集数据流。使用collectAsStateWithLifecycle()必须引入库:
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01"

2.6.0-alpha01是最低版本,因为我是在AGP7.0以下的项目中使用Compose,如需使用更高版本,自行修改吧~
2、 collectAsState() 与 collectAsStateWithLifecycle() 类似,但不是生命周期感知的,通常用于跨平台的场景下(Compose也可以跨平台)。collectAsState 可在 compose-runtime 中使用,因此不需要其他依赖项。
LiveData.obseverAsState()
observeAsState() 会开始观察此 LiveData<T>,并在LiveData<T>有数据更新时,自动将其转换为State<T> ,进而触发可组合项的重组。
//ViewModel层
val mWanLiveData = MutableLiveData<List<WanModel>>()
//UI层
@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun Greeting(vm: ComposeVModel = viewModel()) {
   //将 LiveData<T> 转换成 State<T>
   val liveDataState by vm.mWanLiveData.observeAsState()
   ......
}
使用obseverAsState()需要引入:
implementation "androidx.compose.runtime:runtime-livedata:1.1.1"
注:谷歌建议务必在可组合项中使用 LiveData<T>.observeAsState() 等可组合扩展函数转换类型。
produceState 将对象转换为 State 状态
produceState 会启动一个协程,该协程将作用域限定为可将值推送到返回的 State 的组合。使用此协程将对象转换为 State 状态,例如将外部订阅驱动的状态(如 Flow、LiveData 或 RxJava)引入组合。
即使 produceState 创建了一个协程,它也可用于观察非挂起的数据源。如需移除对该数据源的订阅,请使用 awaitDispose 函数。
看一个官方的示例,展示了如何使用 produceState 从网络加载图像:
@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository
): State<Result<Image>> {
    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new inputs.
    return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)
        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
 Android Framework底层原理篇:https://qr18.cn/AQpN4J
 Android 车载篇:https://qr18.cn/F05ZCM
 Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
 Android 音视频篇:https://qr18.cn/Ei3VPD
 Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
 OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
 Kotlin 篇:https://qr18.cn/CdjtAF
 Gradle 篇:https://qr18.cn/DzrmMB
 Flutter 篇:https://qr18.cn/DIvKma
 Android 八大知识体:https://qr18.cn/CyxarU
 Android 核心笔记:https://qr21.cn/CaZQLo
 Android 往年面试题锦:https://qr18.cn/CKV8OZ
 2023年最新Android 面试题集:https://qr18.cn/CgxrRy
 Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
 音视频面试题锦:https://qr18.cn/AcV6Ap



















