【自学Jetpack Compose 系列】了解Compose(编程思想、状态管理)

580 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天 点击查看活动详情

一、Compose编程思想

1.声明式编程范式

一直以来,Android最常见的界面更新方式是使用findViewById()等函数遍历树,并通过view的一些方法更改节点,通过这些方法改变控件的内部状态。

手动操纵视图会提高出错的可能性:

  • 一条数据在多个位置呈现时,很可能忘记更新显示它的某个视图
  • 当两项更新以意外的方式发生冲突时,很容易造成异常状态。

在近几年,整个行业一开始专项声明性界面模型(例如:Flutter、SwiftUI),改模型大大简化了构建和更新界面关联的工程设计。该技术的工作原理是在概念上从头开始重新生成整个屏幕,然后执行必要的更改。此方法可避免手动更新有状态视图层次结构的复杂性。Compose就是一个声明性界面框架。

2. 可组合函数

比如上一篇文章,我们写的第一个Compose应用程序里面的Greeting就是一个可组合函数。可通过定义一组接收数据并发出界面元素的可组合函数来构建界面。

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

可组合函数中不止可以写页面,也可以将展示页面的逻辑写好。这样写可组合函数的话,不需要在页面中再写一遍赋值操作,从而减少了出错的可能性。如果想修改显示,只需要修改可组合函数的参数就可以了。

简而言之,就是你只需要按照我的要求传值就可以了,至于界面如何操作、如何赋值,我们不需要去关心,那都是组合函数去做就好了

3.重组

在 Compose 中,您可以使用新数据再次调用可组合函数。这样做会导致函数进行重组 -- 系统会根据需要使用新数据重新绘制函数发出的微件。

Compose 框架可以智能地仅重组已更改的组件。-- 这个我们称之为智能重组

在Compose中编程时需要注意的事项:

  • 可组合函数可以按任何顺序执行
  • 可组合函数可以并行执行
  • 重组会尽可能多的可组合函数和lambda
  • 重组是乐观的操作,可能会被取消
  • 可组合函数可能会像动画的每一帧一样非常频繁地运行

在此不做详细阐述,大家要是想详细了解Compose编程思想请访问一下链接: Compose 编程思想  |  Jetpack Compose  |  Android Developers (google.cn)

官方文档绝对靠谱

二、Compose状态管理

1. 状态和组合

由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。因此,TextField 不会像在基于 XML 的命令式视图中那样自动更新。可组合项必须明确获知新状态,才能相应地进行更新。

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       Text(
           text = "Hello!",
           modifier = Modifier.padding(bottom = 8.dp),
           style = MaterialTheme.typography.h5
       )
       OutlinedTextField(
           value = "",
           onValueChange = { },
           label = { Text("Name") }
       )
   }
}

如果运行此代码,您将不会看到任何反应。这是因为,TextField 不会自行更新,但会在其 value 参数更改时更新。这是因 Compose 中组合和重组的工作原理造成的。

2. 可组合项中的状态

可组合函数可以使用 remember 可组合项记住单个对象。系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。remember 既可用于存储可变对象,又可用于存储不可变对象。

注意remember 会将对象存储在组合中,当调用 remember 的可组合项从组合中移除后,它会忘记该对象。 mutableStateOf 会创建可观察的 MutableState<T>,后者是与 Compose 运行时集成的可观察类型。

interface MutableState<T> : State<T> {
    override var value: T
}

value 如有任何更改,系统会安排重组读取 value 的所有可组合函数。

在可组合项中声明 MutableState 对象的方法有三种:

  • val mutableState = remember { mutableStateOf(default) }
  • var value by remember { mutableStateOf(default) }
  • val (value, setValue) = remember { mutableStateOf(default) }

这些声明是等效的,以语法糖的形式针对状态的不同用法提供。

by 委托语法需要以下导入:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

例如,如果您不想在姓名为空时显示问候语,请使用 if 语句中的状态:

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       var name by remember { mutableStateOf("") }
       if (name.isNotEmpty()) {
           Text(
               text = "Hello, $name!",
               modifier = Modifier.padding(bottom = 8.dp),
               style = MaterialTheme.typography.h5
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text("Name") }
       )
   }
}

虽然 remember 可帮助您在重组后保持状态,但不会帮助您在配置更改后保持状态。为此,您必须使用 rememberSaveablerememberSaveable 会自动保存可保存在 Bundle 中的任何值。对于其他值,您可以将其传入自定义 Saver 对象。

3. 其他受支持的状态类型

Jetpack Compose 并不要求您使用 MutableState<T> 存储状态。Jetpack Compose 支持其他可观察类型。在 Jetpack Compose 中读取其他可观察类型之前,您必须将其转换为 State<T>,以便 Jetpack Compose 可以在状态发生变化时自动重组界面。

Compose 附带一些可以根据 Android 应用中使用的常见可观察类型创建 State<T> 的函数:

4. 状态提升

Compose 中的状态提升是一种将状态移至可组合项的调用方以使可组合项无状态的模式。Jetpack Compose 中的常规状态提升模式是将状态变量替换为两个参数:

  • value: T:要显示的当前值

  • onValueChange: (T) -> Unit:请求更改值的事件,其中 T 是建议的新值 以这种方式提升的状态具有一些重要的属性:

  • 单一可信来源:我们会通过移动状态而不是复制状态,来确保只有一个可信来源。这有助于避免 bug。

  • 封装:只有有状态可组合项能够修改其状态。这完全是内部的。

  • 可共享:可与多个可组合项共享提升的状态。如果想在另一个可组合项中执行 name 操作,可以通过变量提升来做到这一点。

  • 可拦截:无状态可组合项的调用方可以在更改状态之前决定忽略或修改事件。

  • 解耦:无状态 ExpandingCard 的状态可以存储在任何位置。例如,现在可以将 name 移入 ViewModel

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

通过从 HelloContent 中提升出状态,更容易推断该可组合项、在不同的情况下重复使用它,以及进行测试。HelloContent 与状态的存储方式解耦。解耦意味着,如果您修改或替换 HelloScreen,不必更改 HelloContent 的实现方式。

5. 在 Compose 中恢复状态

存储状态的方式

添加到 Bundle 的所有数据类型都会自动保存。如果要保存无法添加到 Bundle 的内容,您有以下几种选择。

Parcelize

最简单的解决方案是向对象添加 @Parcelize 注解。对象将变为可打包状态并且可以捆绑。例如,以下代码会创建可打包的 City 数据类型并将其保存到状态。

@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

MapSaver

如果某种原因导致 @Parcelize 不合适,您可以使用 mapSaver 定义自己的规则,规定如何将对象转换为系统可保存到 Bundle 的一组值。

data class City(val name: String, val country: String)

val CitySaver = run {
    val nameKey = "Name"
    val countryKey = "Country"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

ListSaver

为了避免需要为映射定义键,您也可以使用 listSaver 并将其索引用作键:

data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

6. 在 Compose 中管理状态

根据可组合项的复杂性, 有一下不同的管理状态方式:

  • 可组合项: 用于管理简单的界面元素状态
  • 状态容器: 用于管理复杂的界面元素状态,且拥有界面元素的状态和界面逻辑
  • 架构组件ViewModel:一种特殊的状态容器类型,用于提供对业务逻辑以及屏幕或界面状态的访问权限

了解详情

如需详细了解状态 - 在 Jetpack Compose 中使用状态

最后

感谢你看到最后, 最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是沐小枫,一个热爱学习、热爱编程的山东人

(文章内容仅供学习参考, 如有侵权,非常抱歉,请立即联系作者删除。)