0%

Compose

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

  • 仓库依赖使用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

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Composable
@Preview(showBackground = true)
fun HelloWord() {
var name = remember {
mutableStateOf("HelloWord")
}
Column(modifier = Modifier.padding(10.dp)) {
if(!name.value.isEmpty()) {
Text(
text = name.value,
modifier = Modifier.fillMaxWidth())
}
OutlinedTextField(
value = name.value,
label = { Text(text = "Name")},
// 刷新name的值,触发HelloWord重组
onValueChange = {name.value = it })
}
}

image.png

Compose编程思想

声明式编程

  • 命令式编程,关注过程
  • 声明式编程,关注结果(响应式编程,观察者)

命令式编程优势:
eg:一个数据对象会触发多个View模块的渲染场景,命令式编程数据的变化需要我们主动控制多个View的刷新(研发需要关注View),而声明式编程,数据和View是绑定的,数据的变化会自动驱动View的刷新。

组合和重组

  • 可组合函数可以按任何顺序执行

    1
    2
    3
    4
    5
    6
    7
    8
    @Composable
    fun ButtonRow() {
    MyFancyNavigation {
    StartScreen()
    MiddleScreen()
    EndScreen()
    }
    }

    StartScreen、MiddleScreen 和 EndScreen 的调用可以按任何顺序进行,所以不能在StartScreen中修改变量,而让MiddleScreen感知到。相反这3个函数应该保持独立。

  • 可组合函数可以并行运行

  • 重组会跳过尽可能多的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Composable
    fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
    ) {
    Column {
    Text(header, style = MaterialTheme.typography.h5)
    Divider()
    LazyColumn {
    items(names) { name ->
    NamePickerItem(name, onNameClicked)
    }
    }
    }
    }

    如果header发生变化,compose会跳过LazyColumn和Divider的重组。

  • 重组是乐观的操作

如果某个参数在重组完成之前发生更改,Compose 可能会取消重组,并使用新参数开始重组。

  • 可组合函数可能会非常频繁地运行

状态管理

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 系统的实现。

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 进入组合时,它会启动一个协程,并将代码块作为参数传递。如果 LaunchedEffect 退出组合,协程将取消。state值发生变化时,LaunchedEffect会发生重启

rememberCoroutineScope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Composable
fun MoviesScreen() {
val scope = rememberCoroutineScope()

Column {
Button(
onClick = {
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
    @Composable
    fun RememberAnalytics(){
    val name = remember {
    mutableStateOf("song")
    }
    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}")
}
}
}
}