[Android] Study Jetpack Compose

Posted by xiuyuantech on 2023-11-22

Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发,打造生动而精彩的应用。它可让您更快速、更轻松地构建 Android 界面。
Compose的优势:
声明式:直接描述 UI 应该呈现的样子,而不是一步步说明如何实现。
简洁性: 减少模板代码,使得代码更加简洁易读。
可组合性: 通过组合不同的组件来构建复杂的 UI。
工具支持: 完美集成至 Android Studio,提供实时预览和代码完成等功能。

使用

将 Compose 集成到现有项目中,或在新项目中使用它,只需在 Gradle 配置中添加依赖,并确保使用最新版本的 Android Studio,即可开始使用 Compose 构建 UI。相关其资源请参考官方Compose库

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//gradle配置

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}

android {
namespace 'com.example.mycomposedemo'
compileSdk 34

defaultConfig {
applicationId "com.example.mycomposedemo"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
//3、Compose的版本更改为1.5.0
kotlinCompilerExtensionVersion '1.5.0'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}

dependencies {
//compose 核心依赖
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation 'androidx.activity:activity-compose:1.3.1'
implementation 'androidx.compose.material:material:1.1.1'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"

}
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
// Activity主界面逻辑如下

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FormExampleTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ScreenMain()
}
}
}
}
}

@Composable
fun ScreenMain() {
val navController = rememberNavController()

NavHost(navController = navController, startDestination = "main") {
composable("main") { Greeting("Android") }
}
}

@Composable
fun Greeting(name:String,modifier:Modifier = Modifier) {
Text(text="Hello $name",modifier)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
FormExampleTheme {
ScreenMain()
}
}

组件

分类Compose原生
文本TextTextView
文本TextFieldEdittext
图片IconImageView
图片ImageImageView
ButtonButtonButton
ButtonIconButtonButton
ButtonIconToggleButtonCheckBox,Switch
ButtonSwitchSwitch
ButtonRadioButtonRadioButton
ButtonCheckboxCheckBox
布局BoxFrameLayout
布局ColumnLinearLayout
布局RowLinearLayout
布局Scaffold
布局ConstraintlayoutConstraintlayout
列表LazyColumnRecyclerView,ListView
列表LazyRowRecyclerView,ListView
间距类SpacerSpace(估计很多人都没用过)

优化

  1. 避免不必要的重绘
    在 Compose 中,避免不必要的 UI 重绘是提升性能的关键策略。通过合理使用状态和记忆化技术,如 remember 和 derivedStateOf,它们用于优化状态管理和性能,可以显著减少组件的重组次数。

    如果你有一个需要从网络加载的数据列表,可以使用LaunchedEffect并且只在组件首次加载时触发,避免了因为父组件的重组而导致的不必要的网络请求。。

  2. 列表优化
    Compose 提供了 LazyColumn 和 LazyRow 等组件,这些组件只渲染可视区域内的元素,从而优化性能和响应速度。

1
2
3
4
5
6
7
8
9
//合理使用键值对(Keys)在 items 函数中使用 key 参数可以帮助 Compose 更有效地识别和重用元素。
@Composable
fun messageList(messages: List<Message>) {
LazyColumn {
items(messages, key = { message -> message.id }) { message ->
Text(text = message.content)
}
}
}
  1. 避坑或者面试常问
  • ViewModel传递到子可组合项,这样的方式是错误的,同时也会带来内存泄漏的隐患。
    google提示

  • 滥用 remember { mutableStateOf() },如下所示。

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
//错误使用,这会带来不必要的recomposition,从而导致写出来的 Compose UI 出现性能问题。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("") }

Column {
Button(
onClick = {
count++
}
) {
Text("count $count")
}
TextField(
value = text,
onValueChange = {
text = it
}
)
OtherCounter()
}
}

@Composable
fun OtherCounter() {
var text by remember { mutableStateOf("Hello world!") }
Column {
Text(text)
TextField(
value = text,
onValueChange = {
text = it
}
)
}
}

Compose UI 最核心的一个思想就是:状态向下,事件向上。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//正确使用
@Composable
fun CounterRoute(
viewModel: CounterViewModel = viewModel<CounterViewModel>()
) {
val state by viewModel.state.collectAsState()
Counter(
state = state,
onIncrement = {
viewModel.onIncrement()
},
onTextChange = {
viewModel.onTextChange(it)
},
onOtherTextChange = {
viewModel.onOtherTextChange(it)
},
)
}

@Composable
fun Counter(
state: CounterState,
onIncrement: () -> Unit,
onTextChange: (String) -> Unit,
onOtherTextChange: (String) -> Unit,
) {
Column {
Button(
onClick = {
onIncrement.invoke()
}
) {
Text("count ${state.count}")
}
TextField(
value = state.text,
onValueChange = {
onTextChange.invoke(it)
}
)
OtherCounter(
text = state.otherText,
onTextChange = onOtherTextChange,
)
}
}

@Composable
fun OtherCounter(
text: String,
onTextChange: (String) -> Unit,
) {
Column {
Text(text)
TextField(
value = text,
onValueChange = {
onTextChange.invoke(it)
}
)
}
}
  • DisposableEffect、SideEffect、LaunchedEffect之间的区别?

    DisposableEffect:用于在Composable function的生命周期内执行副作用,并在其离开组合时清理这些副作用。适用于需要清理的副作用,例如注册和注销广播接收器。

    SideEffect:用于在组合完成后执行副作用,且这些副作用不会影响UI的重组。适用于不需要清理的副作用,例如记录日志。

    LaunchedEffect:用于在组合完成后启动协程,以执行异步操作。适用于需要异步执行的副作用,例如网络请求。

  • pointer事件在各个Composable function之间是如何处理的?

    在Jetpack Compose中,pointer事件由Modifier.pointerInput处理。每个Composable function都可以使用这个修饰符来拦截和处理pointer事件。事件处理可以通过awaitPointerEventScope和其他相关函数来实现,允许开发者自定义事件的处理逻辑和传播方式。

  • CompositionLocal起什么作用?staticCompositionLocalOf和compositionLocalOf有什么区别?

    CompositionLocal用于在Composable tree中共享数据,而无需显式地将数据作为参数传递。staticCompositionLocalOf和compositionLocalOf的区别在于:

    staticCompositionLocalOf:用于定义静态的CompositionLocal,生命周期与整个应用程序的生命周期一致。
    compositionLocalOf:用于定义动态的CompositionLocal,生命周期与其所在的Composable tree一致。

  • Composable function的状态是如何持久化的?

    Composable function的状态通过remember和rememberSaveable来持久化。remember用于在Composable function重新组合时保持状态,而rememberSaveable在配置更改(如屏幕旋转)时也能保持状态。

  • 如何解决LazyColumn和其他Composable function的滑动冲突?

    可以使用Modifier.nestedScroll来解决LazyColumn和其他Composable function的滑动冲突。这个修饰符允许多个滑动容器协同工作,处理滑动事件的嵌套。

  • Jetpack Compose多线程执行是如何实现的?

    Jetpack Compose通过Kotlin协程来实现多线程执行。LaunchedEffect和rememberCoroutineScope等工具允许在Composable function中启动和管理协程,以执行异步任务。

  • 当一个 Flow 被 collectAsState,应用转入后台时,如果这个 Flow 再进行更新,对应的 State 会不会更新?对应的 Composable 函数会不会更新?

    当应用转入后台时,如果Flow继续更新,collectAsState将继续接收这些更新并更新对应的State。由于Composable函数依赖于这个State,因此它们也会相应地更新。

  1. 建议开启R8
    R8 对于 Compose 的提升是非常巨大的,如果是简单 UI 的话没有 R8 可能还可以用,复杂 UI 下非常推荐开启 R8,代码优化之后的性能的 Debug 的性能差距极大。

  2. 及时关闭资源流
    对于资源管理,例如打开和关闭连接或订阅,及时使用 DisposableEffect 监听onDispose事件,及时关闭资源。

总结

总之,Jetpack Compose 提供了一种现代化、高效且直观的方式来构建 Android 应用的用户界面。但在跨平台上Compose版本仅支持5.0以上,与fultter相比,fultter最低版本支持4.1,可以根据业务选择使用。