Jetpack Compose 是一套用于 Android 应用开发的现代工具包,它采用声明式编程范式来构建 Android 原生界面(UI)。
1,基本概貌如下所述。
声明式编程:
Jetpack Compose 采用声明式编程,即开发者只需描述 UI 应该如何看起来,以及提供数据依赖,而不需要关心 UI 的构建过程。当数据发生变化时,Compose 会自动重新绘制 UI。
可组合函数:
Compose 基于可组合函数来构建 UI。可组合函数是一种通过描述 UI 应该如何看起来和提供数据依赖来定义应用程序 UI 的函数。它们使用 @Composable 注解来标记,表示该函数用于创建 UI。
Compose 的核心是组合函数,这些函数使用 @Composable 注解标记。组合函数可以接受参数并返回 UI 元素,它们可以相互嵌套,形成复杂的 UI 界面。组合函数是无副作用的,即它们不会修改外部状态,只会根据输入参数生成 UI 描述。
Kotlin 编译器插件:
Compose 在 Kotlin 编译器的类型检测和代码生成阶段依赖 Kotlin 编译器插件工作。编译器插件会在编译过程中对可组合函数进行处理,添加额外的参数和调用,以支持 Compose 的执行模式。
UI 树(NodeTree):
Compose 维护了两棵树,一棵是虚拟树(SlotTable),负责树构建和重组,类似于 React 中的 VirtualDOM。另一棵是真实的树(LayoutNode),负责测量和绘制。当应用的状态发生变化时,Compose 会重新执行可能因状态更改而更改的可组合项,然后更新组合以反映所有更改。
状态管理:
Compose 提供了多种状态管理机制,如 mutableStateOf 用于创建可变状态。当状态的值发生变化时,会触发相关组合函数的重组,从而更新 UI。
性能优化:
出于性能考虑,Compose 使用差量更新技术来避免全量重建 UI 树。它只重新绘制那些需要更新的部分,从而提高应用的性能。
2,原理分析
Jetpack Compose采用声明式 UI 框架,其核心设计围绕以下机制。
声明式 UI 范式:
传统命令式 UI是通过 findViewById 获取视图引用,手动修改属性(如 setText())。
Compose 声明式是通过可组合函数描述 UI 状态,框架自动处理状态变化后的 UI 更新。
重组(Recomposition)机制:
当输入参数(如 State)变化时,Compose 重新执行相关可组合函数,生成新的 UI 树。仅更新发生变化的节点(智能重组),通过快照系统跟踪状态依赖关系。
也就是说,当组合函数的输入参数发生变化时,Compose 会自动触发重组过程。重组是指重新执行组合函数以生成新的 UI 描述。Compose 会智能地比较新旧 UI 描述,只更新那些真正发生变化的部分,从而提高性能。
布局系统:
采用单测量策略(Single-pass measurement),通过 LayoutModifier 和ParentDataModifier 实现高效布局。内置 Column、Row、Box 等布局组件,支持自定义布局。
3,引入应用compose所需的依赖
首先,在项目级的build.gradle.kts文件中
plugins {
// alias 是 Gradle 提供的一种简洁引用插件的方式,通常结合 libs.versions.toml 文件使用。
// libs.plugins.androidApplication 引用了 Android 应用插件,该插件用于构建 Android 应用程序,它提供了一系列与 Android 构建相关的任务和配置选项,如资源处理、打包 APK 等。
// apply false:表示暂时不应用这个插件。在多模块项目中,可能会在根项目的 build.gradle.kts 中声明插件,但不立即应用,而是在子模块中根据需要进行应用,这样可以统一管理插件版本。
alias(libs.plugins.androidApplication) apply false
// 使用 alias 引用了 Kotlin Android 插件,该插件用于支持在 Android 项目中使用 Kotlin 语言进行开发。
// 它会配置 Kotlin 编译器,将 Kotlin 代码编译成字节码。
// apply false:同样表示暂时不应用这个插件,以便在子模块中按需使用。
alias(libs.plugins.kotlinAndroid) apply false
}
// buildscript 块用于配置构建脚本本身的依赖项。
// 在 Android 项目中,有些插件或库需要在构建脚本运行时使用,就可以通过 buildscript 块来添加这些依赖。
// buildscript用于定义构建脚本的类路径。在这个块中声明的依赖项只对构建脚本本身可用,而不是对项目的源代码可用。
buildscript {
// dependencies 块用于添加构建脚本所需的依赖项。
dependencies {
// 添加 Jetpack Compose 编译器的依赖项,Jetpack Compose 是 Android 平台上的现代声明式 UI 工具包,其编译器负责将 Compose 代码编译成可执行的代码。
// classpath 表示将这个依赖项添加到构建脚本的类路径中,这样在构建过程中就可以使用该编译器来处理 Compose 代码。
classpath("androidx.compose.compiler:compiler:1.5.3")
}
}
接着在app模块中的build.gradle.kts文件中添加依赖
// 使用 Gradle 的插件 DSL 声明了两个插件
// androidApplication:用于构建 Android 应用程序的插件,提供了与 Android 构建相关的任务和配置选项。
// kotlinAndroid:支持在 Android 项目中使用 Kotlin 语言的插件,负责配置 Kotlin 编译器。
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
}
android {
......
// composeOptions 用于指定 Kotlin 编译器扩展的版本,这个版本要和在项目级 build.gradle 文件中定义的 Compose 版本一致。
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3" as String
}
}
dependencies {
......
// androidx.compose.ui:ui:Compose 的核心 UI 库,包含了构建界面所需的基本组件和布局。
implementation("androidx.compose.ui:ui:1.5.3")
// androidx.compose.material:material:提供了遵循 Material Design 规范的组件,如按钮、文本框等。
implementation("androidx.compose.material:material:1.5.3")
// androidx.compose.ui:ui-tooling-preview:用于在 Android Studio 中预览 Compose 界面。
implementation("androidx.compose.ui:ui-tooling-preview:1.5.3")
// androidx.activity:activity-compose:用于在 Activity 中集成 Compose 界面。
implementation("androidx.activity:activity-compose:1.6.1")
// androidx.compose.ui:ui-test-junit4:用于编写 Compose UI 的测试用例。
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.3")
// androidx.compose.ui:ui-tooling:在调试模式下提供 Compose UI 的工具支持。
debugImplementation("androidx.compose.ui:ui-tooling:1.5.3")
// constraintlayout-compose 是 Jetpack Compose 生态系统中用于构建复杂布局的一个重要库,它允许开发者使用约束布局的方式来排列 Compose 组件。
implementation ("androidx.constraintlayout:constraintlayout-compose:1.1.0")
......
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}
另外,在libs.versions.toml文件中要选择合适的kotlin版本以适配compose编译。
// 定义了项目中使用的各种库和插件的版本号。每个键值对代表一个库或插件的版本
[versions]
agp = "8.4.0" // agp:表示 Android Gradle Plugin 的版本,这里是 8.4.0。
junit = "4.13.2" // junit:JUnit 测试框架的版本为 4.13.2。
kotlin = "1.9.10" // kotlin:Kotlin 语言的版本为 1.9.10。
// 定义了项目中使用的各种库的信息,每个库的配置是一个键值对,其中键是库的别名,值是一个包含库的 group(组织名)、name(库名)和 version.ref(版本引用)的映射。
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
// 定义了项目中使用的插件信息,每个插件的配置是一个键值对,其中键是插件的别名,值是一个包含插件的 id(插件 ID)和 version.ref(版本引用)的映射。
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
进一步深入分析,如果kotlin的版本升级为2.0.0,那么编译时就会出现如下错误:
app:compileReleaseKotlin FAILED
e: This version (1.5.3) of the Compose Compiler requires Kotlin version 1.9.10 but you appear to be using Kotlin version 2.0.0 which is not known to be compatible. Please consult the Compose-Kotlin compatibility map located at https://developer.android.com/jetpack/androidx/releases/compose-kotlin to choose a compatible version pair (or `suppressKotlinVersionCompatibilityCheck` but don't say I didn't warn you!).
对于上面的问题,解决方案是更新 Compose 库到与 Kotlin 2.0.0 兼容的版本。一般来说,较新的 Compose 版本会支持新的 Kotlin 版本。
在模块级的build.gradle.kts(:app)中:
//去掉之前在android {}模块中的代码,即:
// composeOptions 用于指定 Kotlin 编译器扩展的版本,这个版本要和在项目级 build.gradle 文件中定义的 Compose 版本一致。
// composeOptions {
// kotlinCompilerExtensionVersion = "1.5.3" as String
// }
//另外在android{}模块中的buildFeatures模块中去掉对compose的支持
buildFeatures {
viewBinding = true
buildConfig = true
// compose = true // buildFeatures { compose = true } 用于开启项目对 Jetpack Compose 的支持
}
//需要新增如下代码:
//在dependencies{}模块中添加代码,即:
dependencies {
// 使用 Compose BOM 管理版本
implementation(platform("androidx.compose:compose-bom:2024.03.00")) // 选择合适的 BOM 版本
// 添加 Compose 相关依赖
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material:material")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.activity:activity-compose")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation ("androidx.constraintlayout:constraintlayout-compose:1.1.0")
}
// 另外还需要在plugin{}模块添加编译设置
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
// composeCompiler在libs.versions.toml文件中进行定义
alias(libs.plugins.composeCompiler)
}
//在libs.versions.toml文件中
[versions]
agp = "8.4.0"
kotlin = "2.0.0"
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
在项目级的build.gradle.kts文件中:
// 添加上composeCompiler
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.kotlinAndroid) apply false
alias(libs.plugins.composeCompiler) apply false
}
// 去掉下面的路径依赖
//buildscript {
// dependencies {
// classpath("androidx.compose.compiler:compiler:1.5.3")
// }
//}
经过上面的处理,就可以让compose适应2.0.0的kotlin版本。
4,应用Compose代码输出 Hello World.
class KotlinComposeView: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContent 是 ComponentActivity 提供的一个方法,用于设置 Compose 界面。
// 它接受一个 @Composable 函数作为参数,这里传入的是 HelloApp 函数。
setContent {
HelloApp()
}
}
// @Composable 注解:表明这是一个 Compose 组合函数,用于描述 UI 的一部分。
@Composable
fun HelloApp() {
// MaterialTheme 是 Jetpack Compose 提供的一个组件,用于应用 Material Design 主题。
// 它会为其子组件提供默认的颜色、字体等样式。
MaterialTheme {
// Surface 是一个可定制背景、形状和阴影的容器组件,通常用于为内容提供一个视觉上的分隔。
Surface {
// 调用另一个 @Composable 函数 HelloWorldText,用于显示具体的文本内容。
HelloWorldText()
}
}
}
@Composable
fun HelloWorldText() {
// Text:Jetpack Compose 提供的用于显示文本的组件,这里显示的文本内容是 "Hello World"。
Text(text = "Hello World")
}
// @Preview 注解:用于在 Android Studio 的预览窗口中显示 @Composable 函数的预览效果。
// showBackground = true:表示在预览时显示背景,方便查看组件的布局情况。
@Preview(showBackground = true)
@Composable
fun HelloPreView() {
// 调用 HelloApp 函数,将其作为预览内容展示在 Android Studio 中。
HelloApp()
}
}
上面的代码会在页面的左上方显示Hello World。现在如果想让其在布局页面中间显示的话,可以做如下优化。
使用 Column 和 Row 组合布局:
Column 和 Row 是 Compose 中常用的线性布局组件,结合它们的对齐和排列属性可以实现将文本居中显示。
// @Composable 注解:表明这是一个 Compose 组合函数,用于描述 UI 的一部分。
@Composable
fun HelloApp() {
// MaterialTheme 是 Jetpack Compose 提供的一个组件,用于应用 Material Design 主题。
// 它会为其子组件提供默认的颜色、字体等样式。
MaterialTheme {
// Surface 是一个可定制背景、形状和阴影的容器组件,通常用于为内容提供一个视觉上的分隔。
// Modifier 用于修改组件的行为和外观。
// fillMaxSize() 方法会让 Surface 组件填充整个可用空间,即占据整个屏幕。
Surface (modifier = Modifier.fillMaxSize()) {
// Column 是一个线性布局组件,用于在垂直方向上排列子组件。
Column(
// 通过Modifier.fillMaxSize()让 Column 组件填充整个可用空间。
modifier = Modifier.fillMaxSize(),
// Arrangement 是一个枚举类。
// Arrangement.Center 表示将子组件在垂直方向上居中排列。
verticalArrangement = Arrangement.Center,
//Alignment.CenterHorizontally 表示将子组件在水平方向上居中对齐。
horizontalAlignment = Alignment.CenterHorizontally
) {
HelloWorldText()
}
}
}
}
//Row 是 Jetpack Compose 中用于在水平方向排列子组件的线性布局组件。
//使用Row的方式实现如下:
// @Composable 注解:表明这是一个 Compose 组合函数,用于描述 UI 的一部分。
@Composable
fun HelloApp() {
// MaterialTheme 是 Jetpack Compose 提供的一个组件,用于应用 Material Design 主题。
// 它会为其子组件提供默认的颜色、字体等样式。
MaterialTheme {
// Surface 是一个可定制背景、形状和阴影的容器组件,通常用于为内容提供一个视觉上的分隔。
// Modifier 用于修改组件的行为和外观。
// fillMaxSize() 方法会让 Surface 组件填充整个可用空间,即占据整个屏幕。
Surface (modifier = Modifier.fillMaxSize()) {
// Row 是一个线性布局组件,用于在水平方向上排列子组件。
Row(
// 通过 Modifier.fillMaxSize() 让 Row 组件填充整个可用空间。
modifier = Modifier.fillMaxSize(),
// Arrangement.Center 表示将子组件在水平方向上居中排列。
horizontalArrangement = Arrangement.Center,
// Alignment.CenterVertically 表示将子组件在垂直方向上居中对齐。
verticalAlignment = Alignment.CenterVertically
) {
HelloWorldText()
}
}
}
}
使用Box容器实现居中。
// @Composable 注解:表明这是一个 Compose 组合函数,用于描述 UI 的一部分。
@Composable
fun HelloApp() {
// MaterialTheme 是 Jetpack Compose 提供的一个组件,用于应用 Material Design 主题。
// 它会为其子组件提供默认的颜色、字体等样式。
MaterialTheme {
// Surface 是一个可定制背景、形状和阴影的容器组件,通常用于为内容提供一个视觉上的分隔。
// Modifier 用于修改组件的行为和外观。
// fillMaxSize() 方法会让 Surface 组件填充整个可用空间,即占据整个屏幕。
Surface (modifier = Modifier.fillMaxSize()) {
// Box 是一个简单的容器布局,通过设置其内容的对齐方式可以轻松实现居中效果。
Box(
// 通过Modifier.fillMaxSize()让 Box 占据整个父布局的空间。
modifier = Modifier.fillMaxSize(),
// Alignment.Center 使 Box 内的子组件在水平和垂直方向上都居中显示
contentAlignment = Alignment.Center
) {
HelloWorldText()
}
}
}
}
5,Jetpack Compose中的布局
在 Jetpack Compose 中,有多种布局组件可供使用,以满足不同的 UI 设计需求。
Column 布局:Column 用于在垂直方向上排列子组件。
Row 布局:Row 用于在水平方向上排列子组件。
Box 布局:Box 是一个简单的容器布局,可用于重叠或定位子组件。
LazyColumn 和 LazyRow 布局:LazyColumn 和 LazyRow 用于处理大量数据的列表,它们会按需加载和渲染组件,提高性能。
ConstraintLayout 布局:ConstraintLayout 用于创建复杂的相对布局。
@Composable
fun HelloWorldText() {
Column {
// Text:Jetpack Compose 提供的用于显示文本的组件,这里显示的文本内容是 "Hello World"。
Text(text = "Hello World")
// Spacer 用于添加间距,这里通过 Modifier.height(16.dp) 设置了 16dp 的垂直间距。
Spacer(modifier = Modifier.height(16.dp)) // 添加垂直间距
Box(
// Box 组件通过 modifier = Modifier.size(100.dp) 设置了大小为 100dp 的正方形区域。
modifier = Modifier.size(100.dp),
contentAlignment = Alignment.Center
) {
Text(text = "居中显示的文本", color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp)) // 添加垂直间距
Row {
Column {
Text(text = "第一行文本")
Spacer(modifier = Modifier.height(16.dp)) // 添加垂直间距
Text(text = "第二行文本")
}
Spacer(modifier = Modifier.width(16.dp)) // 添加水平间距
Row {
Text(text = "左边文本")
Spacer(modifier = Modifier.width(16.dp)) // 添加水平间距
Text(text = "右边文本")
}
}
Spacer(modifier = Modifier.height(16.dp)) // 添加垂直间距
LazyColumn {
items(5) { index ->
Text(text = "第 $index 项")
}
}
Spacer(modifier = Modifier.height(16.dp)) // 添加垂直间距
// ConstraintSet:用于定义 ConstraintLayout 中各个组件的约束条件。
val constraints = ConstraintSet {
// createRefFor为布局中的组件创建引用,
// createRefFor("constraintText1") 和 createRefFor("constraintText2"):
// 创建了名称为 "constraintText1" 和 "constraintText2" 的引用,后续的约束条件会基于这两个引用进行设置。
// 这些名称将与后续添加到 ConstraintLayout 中的组件的 layoutId 对应。
val text1 = createRefFor("constraintText1")
val text2 = createRefFor("constraintText2")
// constrain 方法用于设置组件的约束条件,如 top.linkTo 和 start.linkTo。
constrain(text1) {
// 将 text1 的顶部和起始边缘分别与父布局的顶部和起始边缘对齐。
top.linkTo(parent.top)
start.linkTo(parent.start)
}
constrain(text2) {
// 将 text2 的顶部与 text1 的底部对齐,起始边缘与 text1 的起始边缘对齐,距离起始边缘50dp。
top.linkTo(text1.bottom)
start.linkTo(text1.start, margin = 50.dp)
}
}
// 创建一个 ConstraintLayout 布局,应用之前定义的约束条件 constraints,并让布局填满整个可用空间。
// ConstraintLayout 根据约束规则布局子组件。
ConstraintLayout(constraints, modifier = Modifier.fillMaxSize()) {
// 添加一个显示 “文本 1” 的 Text 组件,
// 并通过 Modifier.layoutId("constraintText1") 指定其布局 ID,
// 该 ID 与 ConstraintSet 中创建的 "constraintText1" 引用相对应。
Text(text = "文本 1", modifier = Modifier.layoutId("constraintText1")
.height(64.dp)
.width(96.dp)
.background(Color.Red))
// 添加一个显示 “文本 2” 的 Text 组件,
// 并通过 Modifier.layoutId("constraintText2") 指定其布局 ID,
// 该 ID 与 ConstraintSet 中创建的 "constraintText2" 引用相对应。
Text(text = "文本 2", modifier = Modifier.layoutId("constraintText2").size(136.dp).background(Color.Blue))
}
}
}