2-Compose 基础

好处

1. Jetpack Compose 简介与优势

Jetpack Compose 博物馆

 Compose 编译后不是转化为原生的 Android 上的 View 去显示,而是依赖于平台的 Canvas ,在这点上和 Flutter 有点相似,简单地说可以理解为 Compose 是全新的一套 View

Jetpack Compose 是一种声明式 UI 编程框架,采用 Kotlin 语言构建,旨在简化 Android 应用的 UI 开发。与传统的 XML 布局方式不同,Compose 通过 Kotlin 代码直接描述 UI 组件,极大地减少了繁琐的布局嵌套和视图绑定工作,从而提升了代码的简洁性和可维护性。其核心优势包括:

  1. 声明式 UI:Compose 使用声明式编程模型,UI 通过函数式组件描述,避免了传统 UI 开发中繁琐的视图层级结构和 findViewById 调用,降低了出错的风险。例如,直接使用 Kotlin 代码来定义 LinearLayout 和按钮等组件,并且可以在布局中同步嵌入事件处理(如 onClick)。

  2. 与 Kotlin 深度整合:Compose 深度集成 Kotlin,充分利用了 Kotlin 的特性,如扩展函数、Lambda 表达式和类型安全。这使得代码更具可读性和可维护性,同时大大提升了开发效率。

  3. 灵活的控制流:在 Compose 中,你可以嵌入 iffor 等控制语句来动态生成 UI 组件,这使得布局更加灵活和可控。

  4. 提高开发效率

    • 实时预览:Compose 提供了实时预览功能,开发者可以在 Android Studio 中立即看到 UI 组件的变化,而无需启动整个应用程序进行测试。这样,开发者可以更高效地检查界面和修改 UI。
    • 与现有代码兼容:Jetpack Compose 允许与传统的 View 组件混合使用,这意味着你可以在不完全重构项目的情况下逐步引入 Compose。同时,Compose 中的代码可以被混淆,开启混淆后,代码的压缩率较高,有助于提高 APK 的安全性和体积优化。
  5. 简化 UI 管理:Compose 通过 StateLiveData 等机制,使得 UI 状态的管理更加简单。数据变化时,Compose 会自动更新 UI,无需手动操作视图。这避免了传统开发中可能发生的状态同步问题,例如多个视图未及时更新或发生冲突,降低了维护的复杂性。

  6. 减少出错几率:通过声明式的编程模型,减少了手动操控视图的复杂度,避免了因视图更新不及时或错误的视图操作导致的异常状态。如果一条数据需要在多个位置呈现,Compose 确保了所有视图都能根据最新数据自动更新,减少了遗忘更新某个视图的可能性。

综上所述,Jetpack Compose 通过简化 UI 的构建过程、提高开发效率、并且通过更高效的 UI 状态管理,帮助开发者减少重复工作和出错的可能性,从而加速了 Android 应用的开发和维护。

2. 具体实践与应用

接下来,介绍你在项目中使用 Jetpack Compose 的实际应用场景和效果:

1) UI 界面的简化

  • 旧 UI 与新 UI 的对比:可以举例说明,在传统的 XML + View 方式下,复杂的界面可能需要多个布局文件和冗长的逻辑处理,而使用 Compose 后,这些逻辑变得更加直观简洁。举一个具体的 UI 组件的例子,比如一个动态列表、分页加载的界面,如何通过 Jetpack Compose 更加高效地构建。

    例如:

    • 以前使用 XML 时,可能需要写 RecyclerViewAdapter 和多层嵌套的布局文件,而在 Compose 中,通过 LazyColumnItem 即可直接构建,代码量大大减少,开发效率提高。
1
2
3
4
5
LazyColumn {
items(itemsList) { item ->
Text(text = item.name)
}
}

2) 动态 UI 渲染

  • 状态驱动 UI:通过 Jetpack Compose,你可以轻松实现 UI 组件的状态变化,避免了在传统方式中复杂的视图更新和通知。Compose 会根据数据的变化自动更新 UI,无需手动调用 notifyDataSetChangedsetVisibility 等繁琐操作。

    举例:

    • 在使用 MVVM 架构时,你通过 LiveData 或 State 来驱动 UI 变化。比如一个按钮的点击事件,使用 mutableStateOf 来表示 UI 状态,在 UI 层直接绑定状态,Compose 会根据状态自动刷新界面。
1
2
3
4
5
var isClicked by remember { mutableStateOf(false) }

Button(onClick = { isClicked = !isClicked }) {
Text(if (isClicked) "Clicked" else "Click Me")
}

3) 与现有代码的集成

  • Compose 与传统视图的融合:介绍你如何在现有的 Android 项目中逐步引入 Compose,并与传统的 UI 组件(如 FragmentActivityView)进行集成。Jetpack Compose 可以与现有的视图系统共存,可以逐步替代老的 UI 组件。

    比如:

    • 在某些屏幕上,你可能只使用 Compose 来替代部分界面,其他部分仍使用传统的 XML 布局。通过 ComposeView,你可以在现有的 ActivityFragment 中嵌套 Compose UI。

    示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ComposeView(requireContext()).apply {
setContent {
MyComposableScreen()
}
}
}
}

4) 提高开发效率

  • 代码量减少:通过使用 Compose,开发者能够减少布局文件和视图控制器的代码量。例如,常见的视图组件如 ButtonTextView 等都可以通过 Compose 声明式方式快速定义,避免了重复的 XML 和 View 查找操作。

  • 预览功能与热重载:Jetpack Compose 的 @Preview 注解功能允许开发者在不运行应用的情况下查看 UI 渲染效果,极大地提高了迭代开发的速度。而 Compose 的热重载(Hot Reload)功能,开发者可以快速看到修改后的 UI 效果,避免了重新启动应用的时间浪费。

    例如:

    • 你可以通过以下代码快速预览界面效果:
      1
      2
      3
      4
      5
      6
      @Preview
      @Composable
      fun MyPreview() {
      MyComposableScreen()
      }

入门

Jetpack Compose 中的 match_parent 相当于什么?

Compose 编程思想  |  Jetpack Compose  |  Android Developers

Compose 布局基础知识  |  Jetpack Compose  |  Android Developers

原创:写给初学者的Jetpack Compose教程,基础控件和布局

原创:写给初学者的Jetpack Compose教程,Modifier

原创:写给初学者的Jetpack Compose教程,使用State让界面动起来

原创:写给初学者的Jetpack Compose教程,Lazy Layout

[[android编译优化#AGP、kotlin、compose对应版本]]

Composable

告诉编译器:此函数旨在将数据转换为界面。
所有的 Composable 函数还有一个约定俗成的习惯,就是函数的命名首字母需要大写。
@Preview 注解,这个注解表示这个函数是用来快速预览 UI 样式的。

@Composable 注解用于标记一个函数为可组合函数。可组合函数是一种特殊的函数,不需要返回任何 UI 元素,因为可组合函数描述的是所需的屏幕状态,而不是构造界面 widget;而如果按我们以前的 XML 编程方式,必须在方法中返回 UI 元素才能使用它(如返回 View 类型)。

@Composable 注解的函数之间可以相互调用,因为这样 Compose 框架才能正确处理依赖关系。另外,@Composable 函数中也可以调用普通函数,而普通函数中却不能直接调用@Composable 函数。 这里可以类比下 kotlin 中 suspend 挂起函数的用法,其用法是相似的

布局

Compose 通过只测量一次子项来实现高性能。单遍测量对性能有利,使 Compose 能够高效地处理较深的界面树。
image.png
父节点会在其子节点之前进行测量,但会在其子节点的尺寸和放置位置确定之后再对自身进行调整

其他组件

CollapsingToolbarScaffold
stickyHeader
HorizontalPager
BottomNavigationBar
Scaffold
PullRefreshIndicator
TopAppBar

列表

列表和网格  |  Jetpack Compose  |  Android Developers

verticalScroll

我们可以使用 verticalScroll() 修饰符使 Column 可滚动

1
2
3
4
5
6
7
8
@Composable
fun MessageList(messages: List<Message>) {
Column {
messages.forEach { message ->
MessageRow(message)
}
}
}

延迟列表

使用 Compose 的 LazyColumn 和 LazyRow。这些可组合项只会呈现屏幕上显示的元素,因此,对于较长的列表,使用它们会非常高效。

1
2
3
4
5
6
7
8
9
10
import androidx.compose.foundation.lazy.items

@Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageRow(message)
}
}
}

还有一个名为 [itemsIndexed ()]( https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary?hl=zh-cn# (androidx. compose. foundation. lazy. LazyListScope). itemsIndexed (kotlin. collections. List, kotlin. Function2, kotlin. Function2, kotlin. Function3)) 的 items () 扩展函数的变体,用于提供索引

内容内边距

1
2
3
4
5
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
// ...
}

如需在列表项之间添加间距,可以使用 Arrangement.spacedBy ()。以下示例在每个列表项之间添加了 4.dp 的间距:

1
verticalArrangement = Arrangement.spacedBy(4.dp),

性能

早期 Lazy Layout 的性能很差,滚动的时候巨卡无比,确实很难让人用得下去。
但是在 Compose 1.5版本中,Google 做了大量的性能优化工作,所以如果你现在再来尝试一次,你会发现性能已经不是什么问题了。

修饰符

借助修饰符,您可以修饰或扩充可组合项。您可以使用修饰符来执行以下操作:

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

修饰符是标准的 Kotlin 对象。您可以通过调用某个 Modifier 类函数来创建修饰符:

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
// rest of the implementation
}
}
  • 修饰符顺序很重要
  • 提取和重复使用修饰符
  • clickable 使可组合项响应用户输入,并显示涟漪。
  • padding 在元素周围留出空间。
  • fillMaxWidth 使可组合项填充其父项为它提供的最大宽度。
  • size() 指定元素的首选宽度和高度。

偏移量

要相对于原始位置放置布局,请添加 offset 修饰符,并在 x 轴和 y 轴中设置偏移量。偏移量可以是正数,也可以是非正数。padding 和 offset 之间的区别在于,向可组合项添加 offset 不会改变其测量结果:

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(artist.name)
Text(
text = artist.lastSeenOnline,
modifier = Modifier.offset(x = 4.dp)
)
}
}
}

offset 修饰符根据布局方向水平应用。在从左到右的上下文中,正 offset 会将元素向右移,而在从右到左的上下文中,它会将元素向左移。
image.png

requiredSize

请注意,如果指定的尺寸不符合来自布局父项的约束条件,则可能不会采用该尺寸。如果您希望可组合项的尺寸固定不变,而不考虑传入的约束条件,请使用 requiredSize 修饰符:

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.requiredSize(150.dp)
)
Column { /*...*/ }
}
}

在此示例中,即使父项的 height 设置为 100.dpImage 的高度还是 150.dp,因为 requiredSize 修饰符优先级较高。

滚动

在 View 中的话,通常可以在需要滚动的内容之外再嵌套一层 ScrollView 布局,这样 ScrollView 中的内容就可以滚动了。
而 Compose 则不需要再进行额外的布局嵌套,只需要借助 modifier 参数即可,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
@Composable  
fun SimpleWidgetColumn() {
    Row(
        modifier = Modifier
            .fillMaxSize()
            .horizontalScroll(rememberScrollState()),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        ...
    }
}

添加间距 Spacer

1
Spacer(modifier = Modifier.width(8.dp))

Button

如何才能给 Button 指定文字内容呢?它可以和 Text 配合在一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Button(
onClick = { /* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}

Context

要想弹出 Toast 需要有 Context 参数才行。在 Composable 函数当中获取 Context 对象,可以调用 LocalContext. current 获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Composable  
fun SimpleWidgetColumn() {
    Column {
        ...
        val context = LocalContext.current
        Button(onClick = {
            Toast.makeText(context, "This is Toast", Toast.LENGTH_SHORT).show()
        }) {
            Text(
                text = "This is Button",
                color = Color.White,
                fontSize = 26.sp
            )
        }
    }
}

文字图片

Compose 中的文字  |  Jetpack Compose  |  Android Developers

自定义图片  |  Jetpack Compose  |  Android Developers

1
2
3
4
5
6
7
8
9
10
val imageModifier = Modifier
.size(150.dp)
.border(BorderStroke(1.dp, Color.Black))
.background(Color.Yellow)
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Fit,
modifier = imageModifier
)

TextField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Composable  
fun SimpleWidgetColumn() {
    Column {
        ...
        TextField(
            value = "",
            onValueChange = {},
            placeholder = {
                Text(text = "Type something here")
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = Color.White
            )
        )
    }
}

viewmodel

首先我们要引入如下两个库,这是 Compose 为了适配 ViewModel 和 LiveData 而专门设计的库:

1
2
3
4
dependencies {
implementation "androidx. lifecycle: lifecycle-viewmodel-compose: 2.6.2"
implementation "androidx. compose. runtime: runtime-livedata: 1.5.1"
}

传统 LiveData 的用法在 Compose 中并不好使,因为传统 LiveData 依赖于监听某个值的变化,并对相应的界面进行更新,而 Compose 的界面更新则依赖于重组。
因此,我们需要将 LiveData 转换成 State 才行,observeAsState ()函数就是用来做这个事情的,参数中传入的0表示它的初始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import androidx. lifecycle. viewmodel. compose. viewModel

@Composable
fun CallCounter (modifier: Modifier = Modifier, viewModel: MainViewModel = viewModel ()) {
val count by viewModel.count.observeAsState (0)
val doubleCount by viewModel.doubleCount.observeAsState (0)
Column {
Counter (
count = count,
onIncrement = { viewModel.incrementCount () },
modifier.fillMaxWidth ()
)
Counter (
count = doubleCount,
onIncrement = { viewModel.incrementDoubleCount () },
modifier.fillMaxWidth ()
)
}
}

状态

LaunchedEffect

LaunchedEffect 是 Jetpack Compose 中的一个函数,用于在协程中执行副作用操作。副作用操作通常包括异步任务、网络请求、数据库操作或其他可能会阻塞主线程的操作。

LaunchedEffect 函数是一个协程构建器,它接受一个或多个参数,并在代码块中执行异步操作。它会自动在适当的时间启动和取消协程,确保在 Compose 组件的生命周期内正确处理副作用。当组件被创建时,LaunchedEffect 会启动协程,当组件被销毁时,它会自动取消协程。

LaunchedEffect 的参数可以是任何对象,用于标识不同的副作用操作。通常使用简单的数据类对象作为参数,例如 key1 = Unit

LaunchedEffect 的代码块中,你可以执行各种需要在后台进行的操作,例如网络请求、数据库访问、文件读写等。由于这些操作是在协程中执行的,因此它们不会阻塞主线程,确保应用保持响应性。

需要注意的是,LaunchedEffect 函数只能在 Compose 函数内部调用,例如在 @Composable 注解的函数内部使用。如果你尝试在非 Compose 函数中调用它,将会出现编译错误。

总结起来,LaunchedEffect 是一个用于在协程中执行副作用操作的函数,它确保在 Compose 组件的生命周期内正确处理副作用。它是 Jetpack Compose 中处理异步任务和副作用的重要工具之一

1
2
3
4
5
6
itemData?. run {  
LaunchedEffect (key1 = Unit) {
viewModel.getRelateVideoList (itemData. id)
viewModel.saveVideo (itemData)
}
}

DisposableEffect

用于在组件创建和销毁时执行一些副作用操作。当组件被销毁时,onDispose 代码块内的操作会被执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val coroutineScope = rememberCoroutineScope ()

DisposableEffect (Unit) {
val timer = Timer ()
timer.schedule (object : TimerTask () {
override fun run () {
coroutineScope. launch {
pagerState.animateScrollToPage (pagerState. currentPage + 1)
}
}
}, 3000, 3000)
onDispose {
timer.cancel ()
}
}

2-Compose 基础
http://peiniwan.github.io/2025/12/a197a06b33a6.html
作者
六月的雨
发布于
2025年12月16日
许可协议