Android---Jetpack Compose学习007

news2024/11/25 6:53:12

Compose 附带效应

a. 纯函数

纯函数指的是函数与外界交换数据只能通过函数参数和函数返回值来进行,纯函数的运行不会对外界环境产生任何的影响。比如下面这个函数:

fun Add(a : Int, b : Int) : Int {
    return a + b
}

“副作用”(side effect),指的是如果一个操作、函数或表达式在其内部与外界进行了互动,产生运算以外的其他结果,则该操作或表达式具有副作用。

最典型的情况,就是修改了外部环境的变量值。例如如下代码:Add() 函数执行它需要一个外部变量 a,先进行 ++ 操作,然 a + b 返回。只要这个函数一执行,外部变量 a 就会改变。而对于这个 a 所产生的改变,这个就叫做副作用。

var a
fun Add(b : Int) : Unit{
    a++
    return a + b
}

因此,组合函数也是一个函数,那么它也分为有副作用的和没副作用的。而组合函数的副作用和其它函数还有一些差异。

组合函数的特点

a. 执行顺序不定;b. 可以并行运行;c. 可能会非常频繁地运行

处理副作用

虽然我们不希望函数执行中出现副作用,但现实情况是有一些逻辑只能作为副作用来处理。例如一些 IO 操作计时日志埋点等,这些都是会对外界或收到外界影响的逻辑,不能无限制的反复执行。所以 Compose 需要能够合理地处理一些副作用。

\bullet 副作用的执行时机是明确的,例如在 Recomposition 时等。

\bullet 副作用的执行次数是可控的,不应该随着函数反复执行。

\bullet 副作用不会造成泄漏,例如对于注册要提供适当的时机取消注册。

组合函数的副作用

组合函数是主要是用来做 UI 声明的、描述的,只要你在可组合函数内做了与 UI 描述不相关的操作,这一类操作其实都属于副作用。

在 Compose 中可组合函数内部理应只做视图相关的事情,而不应该做函数返回之外的事情,如访问文件等,如果有,那这就叫做附带效应,以下操作全部都是危险的附带效应:

\bullet 写入共享对象的属性;

\bullet 更新 ViewModel 中的可观察项。

\bullet 更新共享偏好设置。

可组合函数应该是无副作用的,但是如果我们要在 Compose 里面使用可组合函数而且会产生附带效应,这时就需要使用 EffectAPI,以便以可预测的方式执行那些副作用。一个 effect,就是一个可组合函数,这个可组合函数不生成 UI,而是在组合完成时产生副作用。

组合函数的生命周期

这些 Effect API 是与我们组合函数的生命周期相关联的。可组合项的生命周期比 activity 和 fragment 的生命周期更简单,一般是进入组合、执行0次或者多次重组、退出组合。

\bullet Enter:挂载到树上,首次显示。

\bullet Composition:重组刷新 UI。

\bullet Leave:从树上移除,不再显示。

组合函数中没有自带的生命周期函数,想要监听其生命周期,需要使用 Effect(附带效应)API :

\bullet LaunchedEffect:第一次调用 Compose 函数的时候调用。

\bullet DisposableEffect:内部有一个 onDispose() 函数,当页面退出时调用。

\bullet SideEffect:compose 函数每次执行都会调用该方法。

LaunchedEffect

如果在可组合函数中进行耗时操作(副作用往往都是耗时操作,例如网络请求、I/O等),就需要将耗时操作放入协程中执行,而协程需要在协程作用域中创建,因此 Compose 提供了 LaunchedEffect 用于创建协程。

\bullet 当 LaunchedEffect 进入组件树时,会启动一个协程,并将 block 放入该协程中执行。

\bullet 当组合函数从视图树上 detach 时,协程还未被执行完毕,该协程也会被取消执行

\bullet 当 LaunchedEffect 在重组时其 key 不变,那 LaunchedEffect 不会被重新启动执行 block。

\bullet 当 LaunchedEffect 在重组时其 key 发生了变化,则 LaunchedEffect 会执行 cancel 后,再重新启动一个新协程执行 block

示例:LaunchedEffect 在初次进入组件树时,就会启动一个协程,调用 block 块执行

1. LaunchedEffectSample.kt

@Composable
fun ScaffoldSample(
    state : MutableState<Boolean>,
    scaffoldState : ScaffoldState = rememberScaffoldState()
){
    // TODO 当我启动这个应用时,组件一开始加载进来,LaunchedEffect() 就会启动一个协程,执行它的 block 块
    //TODO 当 key = state.value 发生改变时(点击按钮时改变),就会启动协程
    LaunchedEffect(state.value){
        // 开启一个弹窗,TODO 是一个 block 块
        scaffoldState.snackbarHostState.showSnackbar(
            // 弹窗内容
            message = "Error message",
            actionLabel = "Retry message"
        )
    }

    // TODO 脚手架
    Scaffold(
        scaffoldState = scaffoldState,
        // 顶部标题栏区域
        topBar = {
            TopAppBar(
                title = { Text(text = "脚手架示例!")}
            )
        },
        // 屏幕内容区域
        content = {
            Box(
                modifier = Modifier.fillMaxSize(), // 填充父容器
                contentAlignment = Alignment.Center // 居中
            ){
                Button(
                    onClick = {
                        //TODO 点击按钮时,弹窗,改变 state 的值。一个动画效果,为耗时操作,即附带效应
                        state.value = !state.value
                    }
                ) {
                    Text(text = "Click it!")
                }
            }
        }
    )
}


@Composable
fun LaunchedEffectSample(){
    val state = remember { mutableStateOf(false) }
    ScaffoldSample(state)
}

2. MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetpackComposeSideEffectsTheme {
                LaunchedEffectSample()
            }
        }
    }
}

上面的示例中,当我们启动 App 时就会让 LaunchedEffect 进入组件树时,启动一个协程,并将 block 放入该协程中执行。可以做如下改变,让进入 App 时不执行 block 块。修改 LaunchedEffect 代码如下:

    if(state.value){
        LaunchedEffect(scaffoldState.snackbarHostState){
        // 开启一个弹窗,TODO 是一个 block 块
        scaffoldState.snackbarHostState.showSnackbar(
            // 弹窗内容
            message = "Error message",
            actionLabel = "Retry message"
        )
        }
    }

rememberCoroutineScope

由于 LauncedEffect 本身就是个可组合函数,因此只能在其他可组合函数中使用。想要在可组合项外启动协程,且需要对这个协程存在作用域限制,以便协程在退出组合后自动取消,可以使用 rememberCoroutineScope。

此外,如果需要手动控制一个或多个协程的生命周期,请使用 rememberCoroutineScope。拿到协程的作用域。

示例:

1. RememberCoroutineScopeSample.kt

@Composable
fun ScaffoldSample(){
    val scaffoldState = rememberScaffoldState()
    // TODO 拿到协程作用域,启动多个协程
    val scope = rememberCoroutineScope()

    Scaffold(
        scaffoldState = scaffoldState,
        //TODO 左侧抽屉栏,点击了菜单按钮时,弹出
        drawerContent = {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "抽屉组件中的内容")
            }                
                        
        },

        // 顶部标题栏区域
        topBar = {
            TopAppBar(
                // 左上角的菜单栏按钮,点击后左侧弹窗
                navigationIcon = {
                    IconButton(
                        onClick = {
                            // TODO 点击菜单按钮时,弹出左侧抽屉栏
                            // TODO 1 启动一个协程
                            scope.launch {
                                // 以动画的形式打开这个抽屉
                                scaffoldState.drawerState.open()
                            }
                        }
                    ) {
                        // 菜单按钮
                        Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
                    }
                },
                title = { Text(text = "脚手架示例!")}
            )
        },

        // 屏幕内容区域
        content = {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "屏幕内容区域")
            }
        },

        // TODO 右下角的悬浮按钮
        floatingActionButton = {
            ExtendedFloatingActionButton(
                text = { Text(text = "悬浮按钮") },
                onClick = {
                    // TODO 2 启动一个协程
                    scope.launch {
                        // 弹窗
                        scaffoldState.snackbarHostState.showSnackbar("点击了悬浮按钮")
                    }
                }
            )
        }
    )
}

2. MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetpackComposeSideEffectsTheme {
                //LaunchedEffectSample()
                ScaffoldSample()
            }
        }
    }
}

在上面代码中,我们通过  val scope = rememberCoroutineScope() 拿到协程作用域,以此来控制多个协程生命周期

rememberUpdatedState

如果 key 值有更新,那么 LaunchedEffect 在重组时就会被重新启动。但是有时候需要在 LaunchedEffect 中使用最新的参数值,但是又不想重新启动 LaunchedEffect,此时就需要用到 rememberUpdatedState。

rememberUpdatedState 的作用是给某个参数创建一个引用,来跟踪这些参数,并保证其值被使用时是最新值,参数被改变时不重启 effect。

示例:RememberUpdatedStateSample.kt

@Composable
fun LandingScreen(onTimeOut : () -> Unit){
    // TODO onTimeOut() 转换成一个状态了
    val currentOnTimeout by rememberUpdatedState(newValue = onTimeOut)
    //TODO key1 = Unit 表示这个 key 值不会变
    LaunchedEffect(key1 = Unit){
        Log.d("HL", "LaunchedEffect")
        repeat(10){
            delay(1000)
            Log.d("HL", "delay ${it + 1}s")
        }

        //
        //onTimeOut()
        currentOnTimeout()
    }
}


@Composable
fun RememberUpdatedStateSample(){
    val onTimeOut1 : () -> Unit = { Log.d("HL", "landing timeout 1") }
    val onTImeOut2 : () -> Unit = { Log.d("HL", "landing timeout 2") }

    // 创建一个 state, 默认值为 onTimeOut1
    val changeOnTimeOutState = remember { mutableStateOf(onTimeOut1) }
    Column {
        Button(
            onClick = {
                // TODO 点击按钮时,改变 changeOnTimeOutState 的值
                if(changeOnTimeOutState.value == onTimeOut1){
                    changeOnTimeOutState.value = onTImeOut2
                }else{
                    changeOnTimeOutState.value = onTimeOut1
                }
            }
        ) {
            Text(text = "choose onTimeOut ${if(changeOnTimeOutState.value == onTimeOut1) 1 else 2}")
        }

        //TODO changeOnTimeOutState.value == OnTimeOut1 / OnTimeOut2
        LandingScreen(changeOnTimeOutState.value)
    }
}

DisposableEffect

DisposableEffect 也是一个可组合函数,当 DisposableEffect 在其 key 值变化或者组合函数离开组件树时,会取消之前启动的协程,并会在取消协程前调用其回收方法进行资源回收相关的操作,可以对一些资源等进行清理。

示例:当开关按钮打开时,拦截返回按钮。

DisposableEffectSample.kt

// 对返回进行一个拦截
@Composable
fun BackHandler(
    backDispatcher : OnBackPressedDispatcher,
    onBack : () -> Unit
){
    // onBack 包装成一个状态, TODO 以便可以随时替换为其它的函数
    val currentOnBack by rememberUpdatedState(newValue = onBack)
    val backCallback = remember {
        object : OnBackPressedCallback(true){
            override fun handleOnBackPressed() {
                //onBack()
                currentOnBack()
            }
        }
    }
    DisposableEffect(key1 = backDispatcher){
        // 开关打开,添加拦截 backCallback
        backDispatcher.addCallback(backCallback)
        // 执行时机为:BackHandler 从组件树中移除,也就是 switch 开关关掉的时候
        onDispose {
            Log.d("HL", "onDispose")
            // 开关一关,从组件树中移除
            backCallback.remove()
        }
    }
}


@Composable
fun DisposableEffectSample(backDispatcher : OnBackPressedDispatcher){
    // TODO 设置一个状态
    var addBackCallback by remember { mutableStateOf(false) }

    Row {
        // 开关按钮
        Switch(
            checked = addBackCallback, // 默认选中或不选中
            onCheckedChange = {
                // 当点击开关进行切换的时候,调用这里的代码
                addBackCallback = !addBackCallback
            }
        )

        Text(text = if (addBackCallback) "Add back callback" else "Not add back callback")
    }

    if(addBackCallback){ // TODO 打开开关,BackHandler() 执行
        BackHandler(backDispatcher){
            Log.d("HL", "onBack")
        }
    }
}

MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetpackComposeSideEffectsTheme {
                //LaunchedEffectSample()
                //ScaffoldSample()
                //RememberUpdatedStateSample()
                DisposableEffectSample(onBackPressedDispatcher)
            }
        }
    }
}

SideEffect

SideEffect 是简化版的 DisposableEffect,SideEffect 并未接收任何 key 值,所以,每次重组,就会执行其 block。当不需要 onDispose、不需要参数控制时使用 SideEffect。SideEffect 主要用来与非 Compose 管理的对象共享 Compose 状态

SideEffect 在组合函数被创建并载入视图树后才会被调用

例如,我们的分析库可能允许通过将自定义元数据(在此示例中为“用户属性”)附加到所有后续分析事件,来细分用户群体。如需将当前用户的用户类型传递给你的分析库,请使用 SideEffect 更新其值。

prodeceState

produceState 可以将非 Compose(如 Flow、LiveData 或 RxJava)状态转换为 Compose 状态。它接收一个 lambda 表达式作为函数体,能将这些入参经过一些操作后生成一个 State 类型变量并返回

\bullet produceState 创建了一个协程,但它也可用于观察非挂起的数据源

\bullet 当 produceState 进入 Composition 时,获取数据的任务被启动,当其离开 Composition 时,该任务被取消。

derivedStateOf

如果某个状态是从其它状态对象计算或派生得出的,请使用 derivedStateOf。作为条件的状态我们称为条件状态。当任意一个条件状态更新时,结果状态都会重新计算

snapshotFlow

使用 snapshotFlow 可以将 State 对象转换为 Flow。snapshotFlow 会运行传入的 block,并发出从块中读取的 State 对象的结果。当在 snapshotFlow 块中读取的 State 对象之一发生变化时,如果新值与之前发出的值不相等,Flow 会向其收集器发出新值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1463497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鱼哥赠书活动第⑧期:《基础软件之路:企业级实践及开源之路》

鱼哥赠书活动第⑧期&#xff1a;《基础软件之路&#xff1a;企业级实践及开源之路》 作者介绍&#xff1a;1.静态分析工具在当前软件开发流程中的应用2.编译相关技术在静态分析工具中的应用3.编译相关技术在提升软件质量和性能上的更多应用4. 未来展望图书推荐&#xff1a;赠书…

[计算机网络]---TCP协议

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一 、TCP协…

springboot防止XSS攻击和sql注入

1. XSS跨站脚本攻击 ①&#xff1a;XSS漏洞介绍 跨站脚本攻击XSS是指攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页之时&#xff0c;嵌入其中Web里面的Script代码会被解析执行&#xff0c;从而达到恶意攻击用户的目的。XSS攻击针对的是用户层面的攻击&…

web安全学习笔记【13】——信息打点(3)

信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&插件项目[1] #知识点&#xff1a; 1、业务资产-应用类型分类 2、Web单域名获取-接口查询 3、Web子域名获取-解析枚举 4、Web架构资产-平台指纹识别 ------------------------------------ 1、开源…

HTML好玩代码合集(1)

VIP代码合集🧧,这一期是场景式HTML代码,里面的文字也是可以修改的,不知道怎么修改可以私信我。 效果(玩个梗,别在意): 好玩代码: <!DOCTYPE html> <html> {#jishugang#}<head><meta charset="utf-8" /><title>怎么堵船了�…

【鸿蒙 HarmonyOS 4.0】UIAbility、页面及组件的生命周期

一、背景 主要梳理下鸿蒙系统开发中常用的生命周期 二、UIAbility组件 UIAbility组件是一种包含UI界面的应用组件&#xff0c;主要用于和用户交互。 UIAbility组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口&#xff1b;一个UIAbility组件中可以通过多个页…

300分钟吃透分布式缓存-08讲:MC系统架构是如何布局的?

系统架构 我们来看一下 Mc 的系统架构。 如下图所示&#xff0c;Mc 的系统架构主要包括网络处理模块、多线程处理模块、哈希表、LRU、slab 内存分配模块 5 部分。Mc 基于 Libevent 实现了网络处理模块&#xff0c;通过多线程并发处理用户请求&#xff1b;基于哈希表对 key 进…

软考-中级-系统集成2023年综合知识(一)

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 软考中级专栏回顾 专栏…

H5星空渐变效果引导页源码

H5星空渐变效果引导页源码 源码介绍&#xff1a;H5星空渐变效果引导页源码是一款带有星空渐变效果的源码&#xff0c;内含3个可跳转旗下站点按钮。 下载地址&#xff1a; https://www.changyouzuhao.cn/8344.html

Java 面向对象进阶 16 接口的细节:成员特点和接口的各种关系(黑马)

成员变量默认修饰符是public static final的原因是&#xff1a; Java中接口中成员变量默认修饰符是public static final的原因是为了确保接口的成员变量都是公共的、静态的和不可修改的。 - public修饰符确保了接口的成员变量可以在任何地方被访问到。 - static修饰符使得接口…

进程线程间的通信:2024/2/22

作业1&#xff1a;代码实现线程互斥机制 代码&#xff1a; #include <myhead.h>//临界资源 int num10;//创建一个互斥锁 pthread_mutex_t mutex;//任务一 void *task1(void *arg) {//获取锁资源pthread_mutex_lock(&mutex);num123;sleep(3);printf("task1:num…

jvm垃圾收集器-三色标记算法

1.对象已死吗? 在堆里面存放着Java世界中几乎所有的对象实例&#xff0c;垃圾收集器在对堆进行回收前&#xff0c;第一件事情就是要确定这些对象之中哪些还“存活”着&#xff0c;哪些已经“死去”&#xff08;即不可能再被任何途径使用的对象). 引计数法 引用计数算法是一…

dubbo源码中设计模式——注册中心中工厂模式的应用

工厂模式的介绍 工厂模式提供了一种创建对象的方式&#xff0c;而无需指定要创建的具体类。 工厂模式属于创建型模式&#xff0c;它在创建对象时提供了一种封装机制&#xff0c;将实际创建对象的代码与使用代码分离。 应用场景&#xff1a;定义一个创建对象的接口&#xff0…

深入理解C语言(5):程序环境和预处理详解

文章主题&#xff1a;程序环境和预处理详解&#x1f30f;所属专栏&#xff1a;深入理解C语言&#x1f4d4;作者简介&#xff1a;更新有关深入理解C语言知识的博主一枚&#xff0c;记录分享自己对C语言的深入解读。&#x1f606;个人主页&#xff1a;[₽]的个人主页&#x1f3c4…

C++ 八数码问题理解 `IDA*` 算法原则:及时止损,缘尽即散

1.前言 八数码是典型的状态搜索案例。如字符串转换问题、密码锁问题都是状态搜索问题。 状态搜索问题指由一种状态转换到到最终状态&#xff0c;求解中间需要经过多少步转换&#xff0c;或者说最小需要转换多少步&#xff0c;或者说有多少种转换方案。本文和大家聊聊八数码问…

【QT 5 +Linux下软件桌面快捷方式+qt生成软件创建桌面图标+学习他人文章+第二篇:编写桌面文件.desktop】

【QT 5 Linux下软件桌面快捷方式qt生成软件创建桌面图标学习他人文章第二篇&#xff1a;编写桌面文件.desktop】 1、前言2、实验环境3、自我学习总结-本篇总结1、新手的疑问&#xff0c;做这件事目的2、了解.desktop3、三个关键目录以及文件编写1、目录&#xff1a;/opt/2、目录…

threeJS 全屏或非全屏状态下鼠标点击获取屏幕位置

使用threeJS引入模型进行点击事件&#xff0c;其实有一个是将获取到坐标位置进行webgl坐标系的转换 全屏状态&#xff1a; 全屏状态下直接利用window.innerWidth和 window.innerHeight进行计算即可&#xff0c;代码如下 // 校验控制器旋转的时候不触发点击事件boxClickEvent(…

【2024软件测试面试必会技能】Selenium(6):元素定位_xpath定位

XPATH是什么 XPATH是一门在XML文档中查找信息的语言&#xff0c;XPATH可用来在XML文档中对元素和属性进行遍历&#xff0c;主流的浏览器都支持XPATH&#xff0c;因为HTML页面在DOM中表示为XHTML文档。Selenium WebDriver支持使用XPATH表达式来定位元素。 Xpath常用如下6种定位…

《论文阅读》e-CARE:探索可解释因果推理的新数据集 ACL2022

《论文阅读》e-CARE:探索可解释因果推理的新数据集 ACL2022 前言简介数据集优势数据集语料级别的统计数据集示例评分标准前言 今天为大家带来的是《e-CARE: a New Dataset for Exploring Explainable Causal Reasoning》 出版:ACL 时间:2022 类型:因果推理 关键词:情绪…