Jetpack Compose状态专篇精讲

目录
  • 1.remember
  • 2.rememberSaveable
  • 3.状态提升
  • 4.状态管理
    • 将Composable作为可信来源
    • 将状态容器作为可信来源
    • 将 ViewModel 作为可信来源

应用中的状态是指可以随时间变化的任何值。这是一个非常宽泛的定义,从 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

到此这篇关于Jetpack Compose状态专篇精讲的文章就介绍到这了,更多相关Jetpack Compose状态内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Compose状态保存rememberSaveable原理解析

    目录 前言 从一个报错说起 rememberSaveable 源码分析 恢复 key 的数据 注册 ValueProvider 注销 registry DisposableSavableStateRegistry 源码分析 saveableStateRegistry 与 SavedStateRegistry DisposableSaveableStateRegistry 与 SaveableStateRegistryImpl canBeSavedToBundle SaveableStateReg

  • Jetpack Compose自定义动画与Animatable详解

    目录 AnimationSpec 1.spring 2.tween 3.keyframes 4.repeatable 5.snap Animatable 本篇主要是自定义动画与Animatable. AnimationSpec 上一篇中,出现了多次animationSpec属性,它是用来自定义动画规范的.例如: fun Modifier.animateContentSize( animationSpec: FiniteAnimationSpec<IntSize> = spring(), fin

  • Android动效Compose贝塞尔曲线动画规格详解

    目录 正文 贝塞尔曲线 解析动画曲线 曲线源码分析 总结 正文 写Compose动画的时候使用animateXAsState的时候会注意到一个参数——animationSpec,如下: val borderRadius by animateIntAsState( targetValue = if (isRound) 100 else 0, animationSpec = tween( durationMillis = 3000, easing = LinearEasing ) ) 此处就不深入探

  • Jetpack Compose Canvas绘制超详细介绍

    目录 1. Canvas 2. 绘制方法 1. drawLine 2. drawRect 3. drawRoundRect 4. drawImage 5. drawCircle 6. drawArc 7. drawPath 8. drawPoints 3. DrawScope拓展方法 1. inset 2. translate 3. rotate与rotateRad 4. scale 5. clipRect 6. drawIntoCanvas 7. withTransform 4.参考 1. C

  • Jetpack Compose常用组件详细介绍

    目录 1. Text 2. Image 3. LazyColumn 1. Text 日常最常用的应该就是显示文字,所以有必要说一下Text控件.首先源码如下: @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = nu

  • docker资源限制和compose部署详解

    目录 一.私有仓库建立 二.Cgroup 资源配置方法 三.CPU使用率控制 使用 stress 工具测试 CPU 和内存 四. CPU 周期限制 五. CPU Core 控制 六. CPU 配额控制参数的混合使用 七. 内存限额 八.Block IO 的限制 九. bps 和 iops 的限制 十. 构建镜像(docker build)时指定资源限制 十一. compose部署 十二. consul部署 总结 一.私有仓库建立 docker pull registry 在docker 引擎终端

  • Jetpack Compose状态专篇精讲

    目录 1.remember 2.rememberSaveable 3.状态提升 4.状态管理 将Composable作为可信来源 将状态容器作为可信来源 将 ViewModel 作为可信来源 应用中的状态是指可以随时间变化的任何值.这是一个非常宽泛的定义,从 Room 数据库到类的变量,全部涵盖在内. 由于Compose是声明式UI,会根据状态变化来更新UI,因此状态的处理至关重要.这里的状态你可以简单理解为页面上展示的数据,那么状态管理就是处理数据的读写. 1.remember remembe

  • MySQL视图和索引专篇精讲

    目录 视图View 代码实现: 索引index 建立索引 删除索引 数据库版本:mysql8.0.27 如果以下代码执行有问题欢迎一起探讨 视图View 什么是视图? 视图是一个虚拟表,是sql语句的查询结果,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据,在使用视图时动态生成.视图的数据变化会影响到基表,基表的数据变化也会影响到视图[insertupdate delete ] ; 创建视图需要create view 权限,并且对于查询涉及的列有select权限:使用cre

  • C/C++堆区专篇精讲

    目录 malloc free new delete memcpy memset malloc malloc开辟堆区内存.头文件stdlib.h,函数原形如下. void*malloc(size_tsize); //返回值void指针,该指针就是开辟的内存地址,参数是开辟的字节数. free free释放堆区开辟的内存.头文件stdlib.h,函数原形如下. voidfree(void*ptr); // 参数传入需要释放的堆区内存首地址. 程序: #include<iostream> #incl

  • Jetpack Compose 实现一个图片选择框架功能

    目录 获取图片 拍照策略 NothingCaptureStrategy FileProviderCaptureStrategy MediaStoreCaptureStrategy 总结 拍照权限 取消拍照导致的脏数据 resolveActivity API 的兼容性 File API 的兼容性 Github 知乎的 Matisse应该蛮多 Android 开发者有了解过或者是曾经使用过,这是知乎在 2017 年开源的一个 Android 端图片选择框架,其颜值在现在看来也还是挺不错的 可惜近几年

  • 剑指Offer之Java算法习题精讲链表专题篇

    题目一  解法 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solut

  • 剑指Offer之Java算法习题精讲二叉树专题篇下

    题目一  解法 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; *

  • 剑指Offer之Java算法习题精讲二叉树专题篇上

    来和二叉树玩耍吧~ 题目一 解法 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val

  • 利用Jetpack Compose实现经典俄罗斯方块游戏

    目录 可组合函数 游戏机身 - TetrisBody 游戏按钮 - TetrisButton 游戏屏幕 - TetrisScreen 调度器 - TetrisViewModel 项目地址 你的童年是否有俄罗斯方块呢,本文就来介绍如何通过 Jetpack Compose 实现一个俄罗斯方块 ~~ 先看下效果图,功能还是挺完善的 就我自己的体验来说,使用 Compose 开发的应用我感受不到和 Android 原生开发之间有什么性能差异,但 Compose 在开发难度上会低很多 Google 官网上

  • vue-路由精讲 二级路由和三级路由的作用

    1.我们继续上一个案例 vue -- 路由精讲制作导航 -- 从无到有 ,在 about文件夹下 创建一些文件夹.如下: 2.编写about.vue代码.当我们点击导航中 "关于我们" ,就会显示该部分内容.代码中写了四个可供点击后可以跳转的模块.和 <router-view></router-view> 表示你点击了哪个组件,哪个组件就会渲染到这里来. 其中注意:css样式,我们直接引入bootstrap中的导航的样式,在标签中直接添加class属性的值就可以

  • Android Jetpack Compose无限加载列表

    目录 前言 方法一: paging-compose 方法二:自定义实现 添加 LoadingIndicator 总结 前言 Android 中使用 ListView 或者 RecycleView 经常有滚动到底部自动 LoadMore 的需求,那么在 Compose 中该如何实现呢? 两种方法可供选择: 基于 paging-compose 自定义实现 方法一: paging-compose Jetpack 的 Paging 组件提供了对 Compose 的支持 dependencies { ..

随机推荐