Android ComposeUI

State

State, MutableState

compose 中, 系统需要监听值的变化, 进行自动重组, 普通对象无法监听变化, 需要使用 State, MutableState

val count = mutableIntStateOf(1)
remember

compose 重组时, 由于 state 声明在函数体中, 会被重新初始化, 为了保证值不变, 加上 remember {}, 保证在重组时, 值不变

val count = remember { mutableIntStateOf(0) }

由于直接用 remember{} 获取的是 State 对象, 使用其 value 值时不方便; 借助 by 委托语法, 直接赋值, 方便使用

var count2 by remember { mutableStateOf(10) }
rememberSaveable

remember 解决了重组过程中的值重新初始化的问题, 但是当 Activity 横竖屏切换时, 依旧会重新初始化; Compose 内部使用 rememberSaveable 来解决

val count3 by rememberSaveable { mutableIntStateOf(1) }

Stateless, Stateful, 状态管理

状态上提
StateHolder
viewModel
LiveData, RxJava, Flow 转 State
转换方法 依赖

LiveData.observeAsState()

androidx.compose:runtime-livedata
Flow.collectAsState()
Observable.subscribeAsState() androidx.compose: runtime-rxjava3

深层嵌套传值

创建 CompositionLocal
val CountLocalContent = compositionLocalOf(neverEqualPolicy()){  
    100  
}
声明 Provider, 提供值
CompositionLocalProvider(CountLocalContent.provides(1001 + count2)) {  
    CompositionLocalProvider(CountLocalContent.provides(1101 + count2)) {  
        CountText(count = count2)  
    }  
}
在子 Composeable 中, 获取值
@Composable  
fun CountLocalText() {  
    val countLocal = CountLocalContent.current  
  
    Text(  
        text = "CountLocalText count Text  $countLocal  "  
    )  
}

副作用

React 类似, Compose 重组, 方法体会重复执行, 但是部分操作并不需要每次都执行; 使用 Effect 去避免这种情况,

SideEffect

在重组成功时执行; Compose 会有多次重组, 但不一定每次都成功; SideEffect 在重组成功时执行

  • 重组成功时执行
  • 可能执行多次

DisposableEffect

React 中的 useEffect 类似, 在指定数据发生变化时执行; 如果传入 Unit 则在 Composeable 整个流程中, 只执行一次

DisposableEffect(vararg keys: Any?) { 
    // register(callback) 
    
    onDispose { 
        // unregister(callback) 
    } 
}

LaunchedEffect

Composeable 中启动协程; 在进入 composition 阶段执行, 在离开 composition 时, 自动取消; 也可以传入 key, 监听值变化时执行

LaunchedEffect(vararg keys: Any?) {
    // do Something async 
}

rememberCoroutineScope

LaunchedEffect 只能在 Compose 函数中执行; 如果在非 Composeable 中执行协程 (eg. Button 的点击事件中), 则可以由 rememberCoroutineScope 获取协程的 scope

@Composable fun Test() { 
    val scope = rememberCoroutineScope() 
    Button( onClick = { 
        scope.launch { // do something } 
        } ) { 
        Text("click me") 
    } 
}

rememberUpdatedState

一般和 LaunchEffectDisposableEffect 一起使用;

  1. LaunchEffect 不监听任何 state 时, 且内部使用传入 Composable 参数时
  2. Composeable 状态变化, 重组传入新值, 由于 LaunchedEffect 和生命周期绑定, 不会重建
  3. LaunchedEffect 获取传入的参数值, 永远是第一次的值
  4. 使用 rememberUpdatedState 则可以获取 Composeable 重组后的新值
    React 也有类似的问题, 当 props 变化时, 内部并不会获取新的值
@Composable  
fun CountText(count: Int) {  
    val count by rememberUpdatedState(newValue = count)  
    // 如果不使用 rememberUpdatedState, 底下LaunchedEffect始终获取的是第一次进入composition阶段的值  
    LaunchedEffect(Unit) {  
        Log.d("zy", "------ LaunchedEffect-- start $count")  
        for (i in 1..10) {  
            delay(1 * 1000L)  
            Log.d("zy", "------ LaunchedEffect- $i >>> $count")  
        }  
  
        Log.d("zy", "------ LaunchedEffect-- end $count")  
    }  
  
    Text(  
        text = "count Text  $count  "  
    )  
}

snapshotFlow

State 值的变化, 转换为 Flow 冷流
上面的 rememberUpdateState, 在 LaunchedEffect 绑定 Composeable 生命周期时, 只能获取到最新的值, snapshotFlow 可以获取到每一次值的变化

val count by rememberUpdatedState(newValue = count)  
  
LaunchedEffect(Unit) {  
    snapshotFlow { count }  
        .collect {  
            delay(1 * 100L)  
            Log.d("zy", "------ LaunchedEffect-- snapshotFlow $count")  
        }  
}

derivedStateOf

根据一个 State 派生出另一个 State;
仅在两个 State 变化状态不同步时使用, 如果两个 State 是同时变化, 就直接使用 源State 更新 UI 了, 使用 derivedStateOf 纯粹添加性能开销

@Composable  
 
fun MessageList(messages: List<Message>) {    
    Box {        
        val listState = rememberLazyListState()
        LazyColumn(state = listState) {            
            // ...        
        }        

        // 根据 listState 派生出 showButton; 
        // showButton 变化频率低, 使用 derivedStateOf,部分UI可以不用重组, 可以降低性能开销
        val showButton by remember {            
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }        
        }        
        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()        
        }    
    }  
}

produceState

开启一个协程, 将普通数据, 转换为 State; eg. 将 Flow, LiveData 之类的数据, 转换成 State
内部有一个 awaitDispose 方法, 可以在离开 Composition 时做清理工作

val countChange = produceState(initialValue = 1) {  
    value = count + 1  
    awaitDispose {  
        // 做清理工作  
    }  
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容