Jetpack Compose 从入门到入门(五)

2023-05-16

应用中的状态是指可以随时间变化的任何值。这是一个非常宽泛的定义,从 Room 数据库到类的变量,全部涵盖在内。

由于Compose是声明式UI,会根据状态变化来更新UI,因此状态的处理至关重要。这里的状态你可以简单理解为页面上展示的数据,那么状态管理就是处理数据的读写。

1.remember

remember就是用来保存状态的,下面举一个小例子。

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       OutlinedTextField(
           value = "",
           onValueChange = { },
           label = { Text("Name") }
       )
   }
}

比如我们在页面中加了一个输入框,如果只是上面代码中这样处理,那你会发现我们输入的文字不会被记录起来,输入框中始终都是空的。这是因为属性value被固定成了空字符串。我们使用remember优化一下:

@Composable
fun HelloContent() {
    val inputValue = remember { mutableStateOf("") }
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = inputValue.value,
            onValueChange = {
                inputValue.value = it
            },
            label = { Text("Name") }
        )
    }
}

通过onValueChange更新value,mutableStateOf 会创建可观察的 MutableState<T>,value 变更时,系统会重组读取 value 的所有Composable函数,这样就会自动更新UI。

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

  • LiveData中可以使用扩展函数 observeAsState()转换为 State。
  • Flow 中可以使用扩展函数 collectAsState()转换为 State。
  • RxJava中可以使用扩展函数subscribeAsState()转换为 State。

2.rememberSaveable

虽然 remember 可帮助您在重组后保持状态,但不会帮助您在配置更改后保持状态。为此,您必须使用 rememberSaveablerememberSaveable 会自动保存可保存在 Bundle 中的任何值。

还是上面的例子,如果我们旋转屏幕,就会发现输入框中的文字会丢失。此时就可以使用rememberSaveable 替换remember 来帮助我们恢复界面状态。

由于保存的数据都是在 Bundle 中的,因此可保存的数据类型是有限制的。比如基础类型、String、Parcelable,Serializable等。一般来说需要保存的对象加个 @Parcelize 注解就可以解决问题。

如果某种原因导致无法使用 @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"))
    }
}

如果你觉得定义map的key麻烦,可以使用 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"))
    }
}

3.状态提升

对于上面使用到rememberrememberSaveState 方法来保存状态的Composable函数,我们称为有状态。有状态的好处是调用方不需要控制状态,并且不必自行管理状态。但是,具有内部状态的Composable往往不易重复使用,也更难测试。

在开发可重复使用的Composable时,您通常想要同时提供同一Composable的有状态和无状态版本。有状态版本对于不关心状态的调用方来说很方便,而无状态版本对于需要控制或提升状态的调用方来说是必要的。

Compose 中的状态提升是一种将状态移至调用方以使可组合项无状态的模式。

举例说明一下状态提升,比如我们实现一个Dialog,为了方便使用我们可以将里面显示的文字,点击事件逻辑写到dialog的内部封装起来,虽然使用简单但不具有通用性。那么为了通用,我们可以将文字,点击事件的回调当参数传入,这样就灵活了起来。

状态提升其实就是这样一个编程思想,只是换了个名词,没有什么特别了。

对于上面输入框的例子,我们用状态提示优化一下:

@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)) {
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

这样就实现了Composable函数HelloContent 与状态的存储方式解耦,便于我们复用。

状态下降、事件上升的这种模式称为“单向数据流”。在这种情况下,状态会从 HelloScreen 下降为 HelloContent,事件会从 HelloContent 上升为 HelloScreen。通过遵循单向数据流,您可以将在界面中显示状态的可组合项与应用中存储和更改状态的部分解耦。

4.状态管理

根据可组合项的复杂性,需要考虑不同的备选方案:

将Composable作为可信来源

用于管理简单的界面元素状态。比如上一篇提到的LazyColumn滚动到指定item,将交互都放在当前的Composable中进行。

	val listState = rememberLazyListState()
    val coroutineScope = rememberCoroutineScope()
    LazyColumn(
        state = listState,
    ) {
       /* ... */
    }

	Button(
        onClick = {
            coroutineScope.launch {
                listState.animateScrollToItem(index = 0)
            }
        }
    ) {
        ...
    }

其实查看rememberLazyListState的源码,可以看到实现很简单:

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex,
            initialFirstVisibleItemScrollOffset
        )
    }
}

将状态容器作为可信来源

当可组合项包含涉及多个界面元素状态的复杂界面逻辑时,应将相应事务委派给状态容器。这样做更易于单独对该逻辑进行测试,还降低了可组合项的复杂性。该方法支持分离关注点原则:可组合项负责发出界面元素,而状态容器包含界面逻辑和界面元素的状态。

@Composable
fun MyApp() {
    MyTheme {
        val myAppState = rememberMyAppState()
        Scaffold(
            scaffoldState = myAppState.scaffoldState,
            bottomBar = {
                if (myAppState.shouldShowBottomBar) {
                    BottomBar(
                        tabs = myAppState.bottomBarTabs,
                        navigateToRoute = {
                            myAppState.navigateToBottomBarRoute(it)
                        }
                    )
                }
            }
        ) {
            NavHost(navController = myAppState.navController, "initial") { /* ... */ }
        }
    }
}

rememberMyAppState代码:

class MyAppState(
    val scaffoldState: ScaffoldState,
    val navController: NavHostController,
    private val resources: Resources,
    /* ... */
) {
    val bottomBarTabs = /* State */

    val shouldShowBottomBar: Boolean
        get() = /* ... */

    fun navigateToBottomBarRoute(route: String) { /* ... */ }

    fun showSnackbar(message: String) { /* ... */ }
}

@Composable
fun rememberMyAppState(
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    navController: NavHostController = rememberNavController(),
    resources: Resources = LocalContext.current.resources,
    /* ... */
) = remember(scaffoldState, navController, resources, /* ... */) {
    MyAppState(scaffoldState, navController, resources, /* ... */)
}

其实就是再封装一层,用户处理逻辑。封装的部分就叫状态容器,用于管理Composable的逻辑和状态。

将 ViewModel 作为可信来源

一种特殊的状态容器类型,用于提供对业务逻辑以及屏幕或界面状态的访问权限。

ViewModel 的生命周期比Composable长,因此不应保留对绑定到组合生命周期的状态的长期引用。否则,可能会导致内存泄漏。建议屏幕级Composable使用 ViewModel 来提供对业务逻辑的访问权限并作为其界面状态的可信来源。如需了解 ViewModel 为何适用于这种情况,请参阅 ViewModel 和状态容器部分。


本篇到此结束,帮忙点个赞~ 给我一点鼓励,你也可以收藏本篇以备不时之需。

参考

  • 状态和 Jetpack Compose
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Jetpack Compose 从入门到入门(五) 的相关文章

  • Jetpack Compose 从入门到入门(十)

    本篇介绍如何将Jetpack Compose 添加到已有应用中 xff0c 毕竟大多数情况都是在现有项目中使用 Jetpack Compose 旨在配合既有的基于 View 的界面构造方式一起使用 如果您要构建新应用 xff0c 最好的选择
  • docker-compose up --build -d 的作用

    docker compose up build d 根据Dockerfile重新下载需要的镜像并构建容器 xff0c 也就是说这句相当于是 docker compose build no cache 和 docker compose up
  • Docker-compose+Dockerfile构建并启动php7.4镜像

    利用官方镜像 43 Dockerfile构建符合自己要求php7 4镜像 DockerFile apt官方源太慢时 xff0c 切换apt源该dockerfile支持的php额外扩展 bcmatch event exif gd mysqli
  • docker-compose.yml传入环境变量参数进容器,springboot使用。

    参考 xff1a https docs docker com compose compose file compose file v3 compose file v34 and under https docs docker com com
  • NVIDIA Jetson TX2 Jetpack 4.5.1 刷机 要点总结

    参考 xff1a Jetson TX2刷机Jetpack4 2 亲测成功版 linkJetson tx2 使用 jetpack 4 3刷机全过程link 注意 连接 TX2 和 host 电脑 usb c 线一定是信号线 xff0c 用信号
  • 使用docker-compose配置mysql数据库并且配置用户密码

    下面要求环境 xff1a 一定要安装docker ce和docker compose才能进行下面步骤 linux找到你要放mysql的目录 创建一个docker compose yml 以下配置了外部数据卷 外部配置文件 外部初始化文件 x
  • 更新docker镜像及容器,使用docker-compose命令

    第一种方法 xff1a docker stop 容器名或镜像id 下面一样 docker rm 容器名 docker rmi 镜像名 docker builder 镜像名 docker run d name 容器名 p 对外端口 内部端口
  • Jetpack Compose 不止是一个UI框架~

    Jetpack Compose是用于构建原生Android UI的现代工具包 Jetpack Compose使用更少的代码 xff0c 强大的工具和直观的Kotlin API xff0c 简化并加速了Android上的UI开发 这是Andr
  • 安装docker-compose报ERROR: Cannot uninstall ‘PyYAML‘. It is a distutils installed project and thus we c

    在CentOS7中 xff0c 如果python版本为3 x xff0c 在安装docker compose时会报错 xff1a ERROR Cannot uninstall PyYAML It is a distutils install
  • Jetpack刷机TX2(大坑)【记录问题】

    Jetpack刷机TX2 xff08 大坑 xff09 1 Jetson TX2 刷机时遇到的坑 xff1a https blog csdn net zshluckydogs article details 79855631 xff01 x
  • docker and docker-compose安装

    docker 安装 curl sSL https get daocloud io docker sh docker compose 安装 1 curl L https get daocloud io docker compose relea
  • Jetson tx2 安装jetpack_3.3手动安装cuda9.0,cudnn7.1

    1 刷机前的准备 xff08 写在前面的话 xff09 装有Ubuntu16 04或者Ubuntu18 04的电脑 xff0c 这里说的电脑可以是台式机也可以是笔记本与TX2区分开来 xff08 电脑是16 04或者18 04无所谓 xff
  • Docker(四)----Docker-Compose 详解

    1 什么是Docker Compose Compose项目来源于之前的fig项目 xff0c 使用python语言编写 与docker swarm配合度很高 Compose 是 Docker 容器进行编排的工具 xff0c 定义和运行多容器
  • Jetpack练手(03):DataBinding

    文章目录 一 导入依赖二 搭建布局三 创建 ViewModel 数据对象四 修改布局为 DataBinding 布局五 绑定数据六 Demo 效果 一 导入依赖 新建 DataBindingDemo 工程 xff0c 参照 LiveData
  • 不贴代码能说明白Jetpack LiveData原理吗(一)

    LifecycleOwner如何提供周期生命周期的变化 LifecycleObserver如何得知生命周期的变化 LiveData的背后隐藏了多少不为人知的秘密 这一切都要从观察者模式说起 起源 何为观察者模式 在代码中最直接的表现就是在事
  • Android Jetpack新成员Compose尝鲜

    前言 Compose的alpha版已经出来有段时间了 前不久的GDG上郭神介绍了Hilt 没曾想居然没有Compose和4 2版本的studio介绍 Compose是google今年在jetpack里新增的一位成员 想着能越过传统的xml
  • Jetpack学习之MVVM实战

    MVVM架构与Jetpack MVVM即Model View ViewModel的缩写 它的出现是为了将图形界面与业务逻辑 数据模型进行解耦 MVVM也是Google推崇的一种Android项目架构模型 而Jetpack组件 大部分是为了能
  • jetpack compose 屏幕适配

    fun Int sdp Dp val screenDp Resources getSystem displayMetrics widthPixels Resources getSystem displayMetrics density re
  • LiveData详细分析2

    一 LiveData是什么东西 1 基于观察者设计模式 LiveData是一种持有可被观察数据的类 被观察者 LiveData需要一个观察者对象 一般是Observer类的具体实现 当观察者的生命周期处于STARTED或RESUMED状态时
  • Android Jetpack 之 DataStore

    1 概述 Google 推出了 JetPack 的新成员 DataStore DataStore 是一种新的数据存储方案 DataStore 以异步 一致的事务方式存储数据 克服了 SharedPreferences 的一些缺点 Jetpa

随机推荐