Android---Jetpack Compose學習007

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, 默認值為 onTimeOut1val 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 / OnTimeOut2LandingScreen(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){// 開關打開,添加攔截 backCallbackbackDispatcher.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.pswp.cn/news/696518.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696518.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696518.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

單例模式的介紹

單例模式&#xff08;Singleton&#xff09;是一種創建型設計模式&#xff0c;它確保一個類只有一個實例&#xff0c;并提供全局訪問點。其核心思想是通過限制類的實例化次數&#xff0c;防止多個實例同時存在&#xff0c;從而避免了多線程競爭和資源浪費&#xff0c;提高了代碼…

【藍橋杯單片機入門記錄】靜態數碼管

目錄 一、數碼管概述 &#xff08;1&#xff09;認識數碼管 &#xff08;2&#xff09;數碼管的工作原理 &#xff08;3&#xff09;LED數碼管驅動方式-靜態顯示 二、數碼管電路圖 三、靜態數碼管顯示例程 &#xff08;1&#xff09;例程1&#xff1a;數碼管顯示某一位&a…

vue、thinkphp實現騰訊云對象存儲COS圖片上傳

環境&#xff1a; thinkphp6 vue2 vant2.12 composer安裝qcloud-sts-sdk composer require qcloud_sts/qcloud-sts-sdk獲取COS臨時id、key的sts接口 <?php declare (strict_types 1);namespace app\index\controller; use QCloud\COSSTS\Sts;class CosController {//h…

如何為PostgreSQL設置自增主鍵?

在 PostgreSQL 中&#xff0c;自增主鍵通常是通過使用 SERIAL 類型或在新版本中使用 IDENTITY 列來實現的。 1. 使用 SERIAL 類型 SERIAL 是一個自動增加的整數&#xff0c;常用于主鍵。當插入新的行時&#xff0c;PostgreSQL 會自動為這個列生成一個新的值。 ??例如 CREAT…

PYQT5-自定義事件

from PyQt5.QtCore import QEvent, QObject from PyQt5.QtWidgets import QApplication import sys# 自定義事件類 class CustomEvent(QEvent):# PYQT5 預留給用戶自定義事件類型的起點為 QEvent.User1000custom_event_type QEvent.registerEventType()# 也可以這樣寫# custom…

2024.2.22

P1162 #include<map> #include<vector> #include<iostream> #include<math.h> #include<algorithm> #include<string> using namespace std; const int N 1020; int n; int g[N][N];//標記數組 int a[N][N];//儲存數組 int dx[] { -1…

webstorm光標變成方塊解決辦法_webstorm光標變粗不能換行

webstorms光標變了 鍵盤上的insert是切換的快捷鍵&#xff0c;敲insert就可以來回切換了

回顧 | Java面向對象 多態篇

多態是面向對象編程中的一個重要概念&#xff0c;它允許不同的對象對同一消息做出不同的響應。 通過多態&#xff0c;可以通過父類或接口定義的引用變量來操作子類或實現類的對象&#xff0c;從而實現同一方法在不同對象上的不同行為。 在Java中&#xff0c;多態性主要通過繼…

雙通道并行網絡,想用哪個網絡用哪個,MATLAB代碼

本期可謂是寶藏篇&#xff01;學會本期的思想&#xff0c;幫助你分分鐘找到創新點&#xff0c;且不與別人重復&#xff01; 本期采用MATLAB代碼&#xff0c;實現一種“基于格拉姆角場與并行CNN的故障診斷方法”。該方法的具體實現可以參考文獻&#xff1a; [1]李宗源,陳謙,錢…

React native更改包名后,啟動app的activity包名不生效問題

這篇文章本不算記錄的&#xff0c;因為實際開發中&#xff0c;類似這種小問題會有很多很多&#xff0c;因為導致問題的原因千奇百怪&#xff0c;解決方案也不盡相同&#xff0c;所以也都沒有記錄。 但今天看到我10年寫的問題解決小文章&#xff0c;被網友收藏了&#xff0c; 感…

普中51單片機學習(EEPROM)

EEPROM IIC串行總線的組成及工作原理 I2C總線的數據傳送 數據位的有效性規定 I2C總線進行數據傳送時&#xff0c;時鐘信號為高電平期間&#xff0c;數據線上的數據必須保持穩定&#xff0c;只有在時鐘線上的信號為低電平期間&#xff0c;數據線上的高電平或低電平狀態才允許…

分享WebGL物體三維建模

界面效果 代碼結構 模型素材類似CT (Computed Tomography)&#xff0c;即電子計算機斷層掃描&#xff0c;它是利用精確準直的X線束、γ射線、超聲波等&#xff0c;與靈敏度極高的探測器一同圍繞物體的某一部位作一個接一個的斷面掃描。 坐標系統 渲染流程 渲染流程是個將之前準…

Sora:OpenAI引領AI視頻新時代

Sora - 探索AI視頻模型的無限可能 隨著人工智能技術的飛速發展&#xff0c;AI視頻模型已成為科技領域的新熱點。而在這個浪潮中&#xff0c;OpenAI推出的首個AI視頻模型Sora&#xff0c;以其卓越的性能和前瞻性的技術&#xff0c;引領著AI視頻領域的創新發展。讓我們將一起探討…

C++(12) 模板類、模板繼承(嚴格模式和自由模式)

文章目錄 模版類1. 模版類2. 模版參數限制3. 模版繼承3.1 嚴格模式3.2 自由模式 4. 模版類的模版函數5. 返回值類型帶有模版 模版類 1. 模版類 #include <iostream>using namespace std;/* 當前 Person 類型&#xff0c;聲明了連個模版分別對應NameType 模版類型&#…

C++ array容器用法詳解

array 容器是 C++ 11 標準中新增的序列容器,簡單地理解,它就是在 C++ 普通數組的基礎上,添加了一些成員函數和全局函數。在使用上,它比普通數組更安全(原因后續會講),且效率并沒有因此變差。 和其它容器不同,array 容器的大小是固定的,無法動態的擴展或收縮,這也就意…

【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 實現微服務的限流、降級、熔斷

目錄 一、Sentinel 介紹1.1 什么是 Sentinel1.2 Sentinel 特性1.3 限流、降級與熔斷的區別 二、實戰演示2.1 下載啟動 Sentinel 控制臺2.2 后端微服務接入 Sentinel 控制臺2.2.1 引入 Sentinel 依賴2.2.2 添加 Sentinel 連接配置 2.3 使用 Sentinel 進行流控&#xff08;含限流…

SLAM ORB-SLAM2(19)特征點三角化

SLAM ORB-SLAM2(19)特征點三角化 1. 前言2. 初始化參數3. 計算投影矩陣4. 恢復三維點4.1. 計算推導4.2. Triangulate5. 檢查三維點5.1. 檢查三維點的深度值和視差角5.2. 檢查空間點的重投影誤差6. 最后處理1. 前言 在 《SLAM ORB-SLAM2(12)估算運動并初始地圖點》 中了解到…

如何將cocos2d-x js打包部署到ios上 Mac M1系統

項目環境 cocos2d-x 3.13 xcode 12 mac m1 big sur 先找到你的項目 使用xcode軟件打開上面這個文件 打開后應該是這個樣子 執行編譯運行就好了 可能會碰到的錯誤 在xcode11版本以上都會有這個錯誤&#xff0c;這是因為iOS11廢棄了system。 將上面代碼修改為 #if (CC_TARGE…

Java 面向對象進階 16 接口的細節:成員特點和接口的各種關系(黑馬)

成員變量默認修飾符是public static final的原因是&#xff1a; Java中接口中成員變量默認修飾符是public static final的原因是為了確保接口的成員變量都是公共的、靜態的和不可修改的。 - public修飾符確保了接口的成員變量可以在任何地方被訪問到。 - static修飾符使得接口…

vue-利用屬性(v-if)控制表單(el-form-item)顯示/隱藏

表單控制屬性 v-if 示例&#xff1a; 通過switch組件作為開關&#xff0c;控制表單的顯示與隱藏 <el-form-item label"創建數據集"><el-switch v-model"selectFormVisible"></el-switch></el-form-item><el-form-item label&…