应用中的状态是指可以随时间变化的任何值。这是一个非常宽泛的定义,从 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状态内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Jetpack Compose状态专篇精讲的更多相关文章

  1. 一文详解 Compose Navigation 的实现原理

    这篇文章主要介绍了一文详解 Compose Navigation的实现原理,文章通告围绕主题展开详细的相关内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

  2. 使用Compose制作抖音快手视频进度条Loading动画效果

    这篇文章主要为大家介绍了使用Compose制作抖音快手视频进度条Loading动画效果,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  3. 利用Jetpack Compose实现绘制五角星效果

    这篇文章主要为大家介绍了Jetpack Compose如何使用自定义操作符实现绘制五角星效果,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

  4. Pinia.js状态管理器上手使用指南

    这篇文章主要为大家介绍了Pinia.js状态管理器上手使用指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  5. 基于React Hooks的小型状态管理详解

    本文主要介绍一种基于 React Hooks 的状态共享方案,介绍其实现,并总结一下使用感受,目的是在状态管理方面提供多一种选择方式。感兴趣的小伙伴可以了解一下

  6. Java JVM中线程状态详解

    这篇文章主要介绍了Java JVM中线程状态详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的朋友可以参考一下

  7. Android Jetpack 狠活Lifecycles与LiveData使用详解

    这篇文章主要为大家介绍了Android Jetpack 狠活Lifecycles与LiveData使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  8. Android开发Jetpack组件Lifecycle使用篇

    这一篇文章来介绍Android Jetpack架构组件的Lifecycle; Lifecycle用于帮助开发者管理Activity和Fragment 的生命周期, 由于Lifecycle是LiveData和ViewModel的基础;所以需要先学习它

  9. jQuery判断checkbox选中状态

    这篇文章主要介绍了jQuery判断checkbox选中状态的相关资料,非常具有参考借鉴价值,感兴趣的朋友一起学习吧

  10. Android Compose自定义TextField实现自定义的输入框

    众所周知Compose中默认的TextField和OutlineTextField样式并不能满足所有的使用场景,所以自定义TextField就成了必备技能。本文将自定义TextField实现自定义的输入框,感兴趣的可以了解一下

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部