我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Jetpack Compose状态专篇精讲

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Jetpack Compose状态专篇精讲

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

    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状态内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Jetpack Compose状态专篇精讲

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Jetpack Compose状态专篇精讲

在今年的Google/IO大会上,亮相了一个全新的 Android 原生 UI 开发框架-Jetpack Compose, 与苹果的SwiftIUI一样,Jetpack Compose是一个声明式的UI框架,这篇文章主要介绍了Jetpack Compose状态管理
2022-11-13

Jetpack Compose入门基础全面精讲

开始布局部分。这部分我个人感觉没有必要每个组件、属性都详细说到,否则篇幅会很长。建立起Compose中的组件与 Android Views的一个对应关系就够了。具体还是需要在实际的使用中去熟悉
2022-11-13

C/C++堆区专篇精讲

一直以来总是对这个问题的认识比较朦胧,我相信很多朋友也是这样的,总是听到内存一会在栈上分配,一会又在堆上分配,那么它们之间到底是怎么的区别呢,让我们一起来看看
2022-11-13

从基础到精通,ASP 状态管理的全面教程,助你成为网站管理专家

ASP 状态管理是一项重要的技术,它允许您在 ASP 页面之间存储和检索信息。在本文中,我们将详细介绍 ASP 状态管理的基础知识,以及如何使用 ASP 状态管理来构建更强大的 Web 应用程序。
从基础到精通,ASP 状态管理的全面教程,助你成为网站管理专家
2024-02-24

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录