0%

JetpackCompose学习2

Android studio

  • Android Studio Flamingo 版本新建项目时已经没有了Empty Compose Activity 模版

image.png
image.png
参考: https://developer.android.com/studio/releases?hl=zh-cn#updates-to-npw-nmw

  • 依赖关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    +--- androidx.compose.material3:material3 -> 1.0.0
    | +--- androidx.compose.foundation:foundation:1.2.0 -> 1.3.0
    | | +--- androidx.annotation:annotation:1.1.0 -> 1.5.0 (*)
    | | +--- androidx.compose.animation:animation:1.1.1 -> 1.3.0
    | | | +--- androidx.annotation:annotation:1.1.0 -> 1.5.0 (*)
    | | | +--- androidx.compose.animation:animation-core:1.3.0
    | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.5.0 (*)
    | | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 (*)
    | | | +--- androidx.compose.foundation:foundation-layout:1.0.0 -> 1.3.0
    | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.5.0 (*)
    | | | | +--- androidx.compose.ui:ui:1.2.0 -> 1.3.0 (*)
    | | | | \--- androidx.compose.ui:ui-unit:1.1.1 -> 1.3.0 (*)
    | | | +--- androidx.compose.runtime:runtime:1.1.1 -> 1.3.0 (*)
    | | | +--- androidx.compose.ui:ui:1.0.0 -> 1.3.0 (*)
    | | | \--- androidx.compose.ui:ui-geometry:1.0.0 -> 1.3.0 (*)
    | | +--- androidx.compose.runtime:runtime:1.3.0 (*)
    | | \--- androidx.compose.ui:ui:1.3.0 (*)
    | +--- androidx.compose.material:material-icons-core:1.0.2 -> 1.3.0
    | | \--- androidx.compose.ui:ui:1.0.0 -> 1.3.0 (*)
    | +--- androidx.compose.material:material-ripple:1.0.0 -> 1.3.0
    | | +--- androidx.compose.foundation:foundation:1.1.1 -> 1.3.0 (*)
    | | \--- androidx.compose.runtime:runtime:1.1.1 -> 1.3.0 (*)
    | +--- androidx.compose.runtime:runtime:1.0.1 -> 1.3.0 (*)
    | +--- androidx.compose.ui:ui:1.3.0 (*)
    | +--- androidx.compose.ui:ui-graphics:1.0.1 -> 1.3.0 (*)
    | \--- androidx.compose.ui:ui-text:1.3.0 (*)
  • libs.versions.toml

https://developer.android.com/build/migrate-to-catalogs

  • gradle 使用kotlin,2022.3.1的版本开始,默认新建项目使用kotlin gradle

https://developer.android.com/build/migrate-to-kotlin-dsl

side effect

LaunchedEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun LaunchedEffectTest() {
val state = remember {
mutableStateOf("xiaomi")
}
LaunchedEffect(state) {
Log.e("LaunchedEffectTest", "request")
delay(3000)//模拟网络操作
state.value = "oppo"
}
Log.e("LaunchedEffectTest", state.value)
Column(modifier = Modifier.padding(10.dp)) {
Spacer(modifier = Modifier.padding(top = 50.dp))
Button(onClick = {
state.value = "vivo"
}) {
Text(text = "按钮")
}
Spacer(modifier = Modifier.padding(top = 100.dp))
Text(text = "手机品牌 ${state.value}")
}
}

LaunchedEffect 内部也维护了生命周期,Composable退出时会自动释放。

rememberCoroutineScope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Composable
fun MoviesScreen() {
// Creates a CoroutineScope bound to the MoviesScreen's lifecycle
val scope = rememberCoroutineScope()

Column {
/* ... */
Button(
onClick = {
// Create a new coroutine in the event handler
scope.launch {
Log.d("MoviesScreen","scope.launch")
}
}
) {
Text("Press me")
}
}
}

rememberCoroutineScope内部会自己维护生命周期,当composable退出时,会自动释放CoroutineScope

rememberUpdatedState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Composable
fun UpdatedStateTest() {
var message = remember { mutableStateOf("start") }
Column(modifier = Modifier.padding(10.dp)) {
Button(
onClick = {
message.value = "clicked"
}
) {
Text("描述信息")
}
LoadingScreen(message.value)
}
}

@Composable
fun LoadingScreen(text: String, scaffoldState: ScaffoldState = rememberScaffoldState()) {
val messageText by rememberUpdatedState(text)
Log.e("LoadingScreen", "start")
LaunchedEffect(true) {
Log.e("LoadingScreen", "delay origin ${messageText}")
delay(4000)
Log.e("LoadingScreen", "delay remember ${messageText}")
scaffoldState.snackbarHostState.showSnackbar(
message = "切换了方法",
actionLabel = messageText
)
}
Scaffold(scaffoldState = scaffoldState) {
Column(modifier = Modifier.padding(it)) {
Text(text = messageText)
}
}
}

LaunchedEffect 中可以感知到messageText的变化

DisposableEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Composable
fun DisposableEffectTest(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
val inputText = remember { mutableStateOf("") }
Log.e("DisposableEffectTest", "Composed")
DisposableEffect(inputText.value) {
// Create an observer that triggers our remembered callbacks
// for sending analytics events
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
Log.e("DisposableEffectTest", "ON_START")
} else if (event == Lifecycle.Event.ON_STOP) {
Log.e("DisposableEffectTest", "ON_STOP")
}
}

// Add the observer to the lifecycle
lifecycleOwner.lifecycle.addObserver(observer)
// When the effect leaves the Composition, remove the observer
onDispose {
Log.e("DisposableEffectTest", "onDispose")
lifecycleOwner.lifecycle.removeObserver(observer)
}
}

Column(modifier = Modifier.padding(10.dp)) {
Button(onClick = {
inputText.value = "按了一下"
}) {
Text(text = "按钮" + inputText.value)
}

}
}

dispose时机:

  • 当value发生变化时触发 onDispose
  • 退出组合

    SideEffect

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Composable
    fun RememberAnalytics(){
    val name = remember {
    mutableStateOf("song")
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
    Log.d("SideEffect","SideEffect")
    }
    Column {
    Text(name.value)
    Button(onClick = { name.value= name.value + "a"}) {
    }
    }
    }
    将compose转为非compose每次重组都会触发 SideEffect

produceState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Composable
fun loadNetworkImage(
url: String,
imageRepository: ImageRepository
): State<Result<ImageBitmap>> {
Log.e("ProduceStateExample", "loadNetworkImage: invoke" )
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading,url, imageRepository) {
// value = Result.Loading
val image = imageRepository.loadNetworkImage(url)
//value 为 MutableState 中的属性
value = if (image == null) {
Result.Error
} else {
Result.Success(image)
}
}
}

//密封类
sealed class Result<T>() {
object Loading : Result<ImageBitmap>()
object Error : Result<ImageBitmap>()
data class Success(val image: ImageBitmap) : Result<ImageBitmap>()
}

将非compose转为compose

derivedStateOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Composable
fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) {
val todoTasks = remember { mutableStateListOf<String>("huawei", "xiaomi", "oppo", "apple", "Compose") }
// 选择 todoTasks中 属于 highPriorityKeywords 的部分
val highPriorityTasks by remember(highPriorityKeywords) {
derivedStateOf { todoTasks.filter { highPriorityKeywords.contains(it) } }
}
Log.e("TodoList", "todoTasks:${todoTasks.toList().toString()}" )
Log.e("TodoList", "highPriorityTasks:${highPriorityTasks.toList().toString()}" )
Column(modifier = Modifier.fillMaxWidth()) {
LazyColumn {
item {
Text(text = "add-TodoTasks", Modifier.clickable {
todoTasks.add("Review")
})
}

item {
Divider(
color = Color.Red, modifier = Modifier
.height(2.dp)
.fillMaxWidth()
)
}


items(highPriorityTasks) { Text(text = it) }
item {
Divider(
color = Color.Red, modifier = Modifier
.height(2.dp)
.fillMaxWidth()
)
}
items(todoTasks) {
Text(text = it)
}
}
}
}

snapshotFlow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun SnapshotFlow() {
Box(modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center) {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
items(500) { index ->
Text(text = "Item: $index")
}
}
Log.e("SnapshotFlow", "Recompose")
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 4 }
.distinctUntilChanged()
.filter { it }
.collect {
Log.e("SnapshotFlow", "snapshotFlow${it}")
}
}
}
}

原理

源码

1
2
3
4
5
6
7
8
@Composable
fun HelloContent() {
val name = remember {
mutableStateOf("songpengfei")
}

Text(text = name.value)
}

产物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static final void HelloContent(Composer composer, int i) {
Composer composer2;
Composer startRestartGroup = composer.startRestartGroup(-1522556028);
ComposerKt.sourceInformation(startRestartGroup, "C(HelloContent)");
if (i != 0 || !startRestartGroup.getSkipping()) {
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventStart(-1522556028, i, -1, "com.compose.sample.HelloContent (MainActivity.kt:296)");
}
startRestartGroup.startReplaceableGroup(-492369756);
ComposerKt.sourceInformation(startRestartGroup, "CC(remember):Composables.kt#9igjgp");
Object rememberedValue = startRestartGroup.rememberedValue();
if (rememberedValue == Composer.Companion.getEmpty()) {
rememberedValue = SnapshotStateKt.mutableStateOf$default("songpengfei", null, 2, null);
startRestartGroup.updateRememberedValue(rememberedValue);
}
startRestartGroup.endReplaceableGroup();
String str = (String) ((MutableState) rememberedValue).getValue();
composer2 = startRestartGroup;
TextKt.m1639TextfLXpl1I(str, null, 0L, 0L, null, null, null, 0L, null, null, 0L, 0, false, 0, null, null, composer2, 0, 0, 65534);
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventEnd();
}
} else {
startRestartGroup.skipToGroupEnd();
composer2 = startRestartGroup;
}
ScopeUpdateScope endRestartGroup = composer2.endRestartGroup();
if (endRestartGroup == null) {
return;
}
endRestartGroup.updateScope(new MainActivityKt$HelloContent$1(i));
}

状态

remember

  • 可组合函数可以使用 remember API 将对象存储在内存中,系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。

    1
    2
    3
    var value1 = remember { mutableStateOf("songpengfei") }
    var value2 by remember { mutableStateOf("songpengfei") }
    val (value, setValue) = remember { mutableStateOf("songpengfei") }

    mutableStateOf返回了 MutableState,compose会观察MutableState中值的变化来进行重组。

  • remember 返回的对象会将对象缓存到内存中,所以在创建对象开销很大的场景中也可以使用remember来缓存。

    1
    var person = remember("key") { Person() }

    当key发生变化时,person会重新创建

    rememberSaveable

    rememberSaveable 当组件配置发生变化导致重启时,可以恢复数据。

  • Parcelize 对象序列化

  • mapSaver

  • listSaver

    状态提升

    image.png

单向数据流模式,状态下降,事件上升;
适合状态提升的场景:

生命周期

image.png
组合中可组合项的生命周期。进入组合,执行 0 次或多次重组,然后退出组合

Column生命周期

1
2
3
4
5
6
7
8
@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
MovieOverview(movie)
}
}
}

向列表尾部添加数据
image.png
向列表头部添加时,所有的MovieOverview都会重组。
image.png

1
2
3
4
5
6
7
8
9
10
@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
key(movie.id) { //id为唯一值
MovieOverview(movie)
}
}
}
}

image.png

Modifier

  • 更改可组合项的大小、布局、行为和外观
  • 添加信息,如无障碍标签
  • 处理用户输入
  • 添加高级互动,如使元素可点击、可滚动、可拖动或可缩放

    阶段

    image.png
  1. 组合:要显示什么样的界面。Compose 运行可组合函数并创建界面说明。
  2. 布局:要放置界面的位置。该阶段包含两个步骤:测量和放置。对于布局树中的每个节点,布局元素都会根据 2D 坐标来测量并放置自己及其所有子元素。
  3. 绘制:渲染的方式。界面元素会绘制到画布(通常是设备屏幕)中。

image.png
状态在每个阶段都可以读取,不一定非要在重组阶段。所以当postion位置发生变化时,我们可以只触发Layout和Drawing,不需要进行重组。

架构分层

image.png

  • Runtime

此模块提供了 Compose 运行时的基本组件,例如 remember、mutableStateOf、@Composable 注释和 SideEffect。

  • UI

界面层由多个模块(ui-text、ui-graphics 和 ui-tooling 等)组成。这些模块实现了界面工具包的基本组件,例如 LayoutNode、Modifier、输入处理程序、自定义布局和绘图。

  • Foundation

此模块为 Compose 界面提供了与设计系统无关的构建块,例如 Row 和 Column、LazyColumn、特定手势的识别等。

  • Material

此模块为 Compose 界面提供了 Material Design 系统的实现。