目录
[1.Android Studio 版本要求](#1.Android Studio 版本要求)
[2.Gradle 配置](#2.Gradle 配置)
[2.1 项目级 build.gradle](#2.1 项目级 build.gradle)
[2.2 模块级 build.gradle](#2.2 模块级 build.gradle)
[3.1 composable注解:](#3.1 composable注解:)
[1.1 Text常见属性](#1.1 Text常见属性)
[1.2 代码演示](#1.2 代码演示)
[1.3 需要响应式更新 UI](#1.3 需要响应式更新 UI)
[2.1 Button常见属性](#2.1 Button常见属性)
[2.2 代码演示](#2.2 代码演示)
[3.1 Image常见属性](#3.1 Image常见属性)
[3.2 完整代码](#3.2 完整代码)
[3.3 加载网络图片](#3.3 加载网络图片)
[3.3.1 导入 Compose 库和 网络库:](#3.3.1 导入 Compose 库和 网络库:)
[3.3.2 要加载图像,请使用 AsyncImage可组合:](#3.3.2 要加载图像,请使用 AsyncImage可组合:)
[1.1 文本输入 (TextField/TextField)](#1.1 文本输入 (TextField/TextField))
[1.2 滑块 (Slider)](#1.2 滑块 (Slider))
[1.3 开关 (Switch/Checkbox)](#1.3 开关 (Switch/Checkbox))
[2.与传统 Android View 的区别](#2.与传统 Android View 的区别)
[3.1 输入验证](#3.1 输入验证)
[3.2 格式化输入](#3.2 格式化输入)
[3.3 与 ViewModel 交互](#3.3 与 ViewModel 交互)
一、什么是Compose?
Compose被称为声明式UI框架,开发者只需描述UI应该是什么样的?它会呈现什么样的状态?而不需要一步步指定如何达到这个状态。而传统的UI开发体系(命名式UI)比较复杂,需要先用xml定义布局样式,然后再通过java或者kotlin语言把布局与页面关联,之后还需要我们一步步的指定UI应该如何达到某种样式状态,过程繁琐、代码冗余。
尽管绝大多数安卓开发团队依然还在使用xml,但声明式UI是前端开发体系的发展趋势,常见的声明式UI框架体系:React、Flutter、SwiftUI、Compose、arkts
1.声明式与命令式UI的区别
|------|------------------|---------------------------|
| 特性 | 声明式UI (如Compose) | 命令式UI (如传统Android View系统) |
| 编程方式 | 描述UI应该是什么样子 | 详细说明如何创建和更新UI |
| 状态管理 | 自动跟踪状态变化 | 需要手动管理状态和UI同步 |
| 更新机制 | 智能差分更新 | 需要显式调用更新方法 |
| 代码结构 | 更简洁、声明性 | 更详细、过程性 |
| 学习曲线 | 需要理解响应式概念 | 更符合传统编程思维 |
二、Compose环境
1.Android Studio 版本要求
最低支持版本:
- Android Studio Arctic Fox (2020.3.1) 及以上(对应 AGP 7.0+ 和 Kotlin 1.5.30+)
- 目前官网推荐下载的版本,完全足够
2.Gradle 配置
2.1 项目级 build.gradle
bash
// 确保有以下配置:
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
}
2.2 模块级 build.gradle
下面的版本一般不需要修改,正常情况下,你创建的时候是什么版本,那直接用默认版本就好
bash
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
buildFeatures {
compose = true // 启用 Compose
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3" // 与 Kotlin 版本对应
}
}
dependencies {
// 基础依赖包
implementation("androidx.compose.ui:ui:1.5.4")
implementation("androidx.compose.material:material:1.5.4")
implementation("androidx.compose.ui:ui-tooling-preview:1.5.4")
debugImplementation("androidx.compose.ui:ui-tooling:1.5.4")
// 可选扩展库
implementation("androidx.activity:activity-compose:1.8.0") // 替代 ComponentActivity
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") // ViewModel 支持
}
3.简单案例
Kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WelcomeCard(name = "laosun")
}
}
@Composable
fun WelcomeCard(name: String) {
Text(text = "hello $name")
}
}
3.1 composable注解:
@Composable 是 Jetpack Compose 的核心注解,标志着函数具有特殊能力:声明式构建 UI并 自动响应状态变化。
- 命名规范:首字母大写
Kotlin
// ✅ 正确:函数首字母大写
@Composable
fun ProductCard(product: Product)
// ❌ 错误:首字母小写
@Composable
fun productCard(product: Product) // 编译警告
- 调用限制:只能在其他@Composable中被调用
Kotlin
@Composable
fun Parent() {
Child() // ✅ 只能在其他@Composable中调用
}
fun regularFunction() {
Child() // ❌ 禁止在普通函数中调用
}
- 参数传递规则:
Kotlin
@Composable
fun UserProfile(
user: User, // ✅ 只读数据类
onLogout: () -> Unit, // ✅ 事件回调
modifier: Modifier = Modifier // ✅ 修饰符最后
) {
// ❌ 禁止修改参数!
// user.name = "NewName"
}
三、常见组件
1.Text
Text组件和view体系中的TextView类似,会提供绝大多数常见的文本相关属性,足以涵盖90%的开发需求。
1.1 Text常见属性
|---------------------|-----------------------------------------------------------|--------------|
| XML 属性 | Compose 等价实现 | 说明 |
| android:text | text = "内容" | 文本内容 |
| android:textColor | color = Color.Red | 文本颜色 |
| android:textSize | fontSize = 16.sp | 字号(必须加.sp单位) |
| android:fontFamily | fontFamily = FontFamily.Serif | 字体家族 |
| android:lineSpacing | lineHeight = 24.sp | 行高 |
| android:maxLines | maxLines = 2 | 最大行数 |
| android:ellipsize | overflow =TextOverflow.Ellipsis | 溢出处理方式 |
| android:gravity | textAlign = TextAlign.Center | 对齐方式 |
| android:shadowColor | shadow = Shadow(...) | 文字阴影 |
| android:textStyle | fontWeight = FontWeight.Bold fontStyle = FontStyle.Italic | 粗体/斜体 |
1.2 代码演示
Kotlin
@Composable
fun WelcomeCard(textState: String) {
Text(
text = textState, // 绑定文本内容
modifier = Modifier //Modifier用来修饰、配置组件的外观和一些行为,比如大小、边距、点击、背景
.fillMaxWidth() // 充满宽度
.padding(16.dp) // 内边距 这里的dp本质是为Int添加了扩展函数
.background(Color.LightGray) // 背景色
.clickable { // 点击事件
Toast.makeText(this, "welcome to compose", Toast.LENGTH_SHORT).show()
},
color = Color(0xFF6200EE), // 文字颜色 (ARGB格式)
fontSize = 18.sp, // 字体大小
fontWeight = FontWeight.Bold, // 字体粗细
fontStyle = FontStyle.Italic, // 字体样式
fontFamily = FontFamily.Monospace, // 字体类型
letterSpacing = 0.5.sp, // 字符间距
lineHeight = 24.sp, // 行高
overflow = TextOverflow.Ellipsis, // 溢出显示省略号
maxLines = 1, // 最大行数
textAlign = TextAlign.Center, // 居中对齐
style = androidx.compose.ui.text.TextStyle( // 文本样式组合
shadow = Shadow(
color = Color.Black,
offset = Offset(2f, 2f),
blurRadius = 4f
)
)
)
}
1.3 需要响应式更新 UI
Compose 是声明式 UI 框架,当需要更新UI的时候,它需要知道哪些数据变了?哪些 UI 部分需要更新?mutableStateOf可以解决这个问题,类似于LiveData,添加一个观察者 。创建可观察的状态,当这个状态发生变化时,会自动触发UI的重组,让UI刷新。总的来说就是:在数据变化时自动更新 UI。
但因为函数每次执行(重组)时,mutableStateOf都会重新创建对象,所以值也会被重置。如果需要放置数据被反复创建,此时mutableStateOf无法单独存在。需要加上remember。
remember 是 Compose 的一个状态记忆工具,主要解决以下问题:
- 在重组中保持状态:确保状态在 Composable 函数多次执行(重组)时不被重置
- 避免重复初始化:防止每次重组都重新创建对象或重新计算值,避免过多的内存开销
Kotlin
@Composable
fun ClickTest() {
var i by remember { mutableStateOf(0) }
Text(text = "当前i = ${i}",
fontSize = 38.sp,
modifier = Modifier.clickable {
i += 1
Log.i(TAG, "ClickTest: $i ")
})
}
注意:
- 如果是int、double等类型,可以优先使用 mutableXxxStateOf,如果没有对应的类型,那就直接用mutableStateOf
- 没有mutableStateOf→ UI 不会更新
- 没有remember→ 状态会被重置,因此mutableStateOf和remember二者协作才能实现稳定的响应式状态。
2.Button
2.1 Button常见属性
|--------------------|-------------------------------------------|------|
| XML 属性 | Compose 等价实现 | 说明 |
| android:text | Text("按钮文字")作为子元素 | 按钮文本 |
| android:onClick | onClick = { ... } | 点击事件 |
| android:background | colors = ButtonDefaults.buttonColors(...) | 背景色 |
| android:enabled | enabled = false | 禁用状态 |
| android:elevation | elevation = ButtonDefaults.elevation() | 阴影高度 |
| android:minHeight | modifier.height(48.dp) | 最小高度 |
2.2 代码演示
Kotlin
/**
* 按钮button的常见用法
*/
@Composable
fun ButtonTest1() {
var isEnable by remember { mutableStateOf(true) }
Button(
onClick = {
isEnable = false
Toast.makeText(this, "按钮被点击了", Toast.LENGTH_SHORT).show()
},
modifier = Modifier.padding(16.dp),
enabled = isEnable,
colors = ButtonDefaults.buttonColors(
containerColor = Color.Blue,//正常状态背景色
contentColor = Color.Red,//文字颜色
disabledContainerColor = Color.Gray,//被禁用的背景颜色
disabledContentColor = Color.White//禁用的文字颜色
)
) {
Text(text = "我是一个按钮")
}
}
因为Button最主要的任务就是处理点击事件,所以需要强制onClick,直接在onclick中就把点击事件消费完了,导致其他后续事件失效。如果需要高级交互事件,建议自定义view或者使用text。Text组件本身不处理触摸事件,因此没有任何内置手势冲突:
- pointerInput:用于处理各种指针输入事件(如触摸、鼠标、手写笔等事件),它提供了底层的输入处理能力,允许开发者自定义手势识别和交互逻辑,接受PointerInputScope类型。
- detectTapGestures是 PointerInputScope的一个扩展函数,专门用于识别常见的点击类手势。它封装了常见的点击相关手势的检测逻辑,简化了开发者的使用。
|-------------|------------|-----------------|
| 手势类型 | 说明 | 默认触发条件 |
| onPress | 当用户按下手指时触发 | 初始按压 |
| onTap | 短按/单击事件 | 按压后快速释放(≈200ms) |
| onDoubleTap | 双击事件 | 连续两次快速点击(间隔 |
| onLongPress | 长按事件 | 按压持续时间>500ms |
Kotlin
@Composable
fun ButtonTest1() {
Text(text = "长按按钮",
modifier = Modifier
.padding(16.dp)
.pointerInput(Unit) {
detectTapGestures(
onTap = { /* 点击事件处理 */
Log.i(TAG, "buttonTest1: onTap")
},
onLongPress = {
Log.i(TAG, "buttonTest1: onLongPress")
},
onDoubleTap = {
Log.i(TAG, "buttonTest1: onDoubleTap")
}
)
}
)
}
3.Image
图片加载组件,相当于view体系中的ImageView
3.1 Image常见属性
|--------------------------|-------------------------------------|------|
| XML 属性 | Compose 等价实现 | 说明 |
| android:src | painter = painterResource(id) | 图片资源 |
| android:scaleType | contentScale = ContentScale.Crop | 缩放模式 |
| android:tint | colorFilter = ColorFilter.tint(...) | 着色 |
| android:alpha | alpha = 0.5f | 透明度 |
| android:adjustViewBounds | modifier.aspectRatio(ratio) | 宽高比 |
3.2 完整代码
Kotlin
var grayscale by remember { mutableStateOf(false) } // 1. 灰度模式状态
Image(
painter = painterResource(R.drawable.landscape), // 2. 加载资源图片
contentDescription = "风景图片", // 3. 无障碍描述(必需!)
modifier = Modifier
.fillMaxWidth() // 4. 最大宽度
.height(200.dp) // 5. 固定高度
.clip(RoundedCornerShape(12.dp)) // 6. 圆角裁剪
.clickable { grayscale = !grayscale }, // 7. 点击切换状态
contentScale = ContentScale.Crop, // 8. 裁剪缩放模式
alignment = Alignment.TopCenter, // 9. 图片在容器内对齐
alpha = if (grayscale) 0.8f else 1f, // 10. 透明度动态变化
colorFilter = if (grayscale) // 11. 颜色滤镜(动态)
ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
else null
)
3.3 加载网络图片
如果需要加载网络图片,建议使用compose中推荐的图片加载库coil:https://github.com/coil-kt/coil/blob/main/README-zh.md
3.3.1 导入 Compose 库和 网络库:
bash
//如果kotlin版本是2.2.0,可以使用如下版本
implementation("io.coil-kt.coil3:coil-compose:3.3.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.3.0")
//如果kotlin版本是1.9.0,可以使用如下版本
implementation("io.coil-kt:coil-compose:2.6.0")
3.3.2 要加载图像,请使用 AsyncImage可组合:
Kotlin
AsyncImage(
model = "https://example.com/image.jpg",
modifier = Modifier.fillMaxWidth(),
placeholder = painterResource(R.drawable.placeholder), // 占位图
error = painterResource(R.drawable.error_image), // 加载失败图
contentDescription = null,
contentScale = ContentScale.Crop //保持宽高比例,填满容器并且裁剪多余部分
)
四、onValueChange
onValueChange是一个回调函数参数 ,用于监听用户输入或交互控件的值变化。它是 Compose 中处理动态数据流的核心机制之一,尤其在可交互组件(如输入框、滑块、开关等)中高频使用。当用户操作修改组件的值时(如输入文字、拖动滑块、切换开关),onValueChange 会立即触发,并将新值作为参数传递出来。
1.使用场景
1.1 文本输入 (TextField/TextField)
Kotlin
var text by remember { mutableStateOf("") }
TextField(
value = text, // 当前显示的值(绑定状态)
onValueChange = { newText ->
text = newText // 更新状态
}
)
- 逻辑:用户每输入一个字符,onValueChange 就会触发,将新文本 newText 赋值给状态变量 text,触发重组并更新 UI。
1.2 滑块 (Slider)
Kotlin
var sliderValue by remember { mutableStateOf(0f) }
Slider(
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue // 更新状态(Float 类型)
},
valueRange = 0f..100f
)
- 逻辑:用户拖动滑块时,实时回调新的 newValue(Float 类型)。
1.3 开关 (Switch/Checkbox)
Kotlin
var isChecked by remember { mutableStateOf(false) }
Switch(
checked = isChecked,
onCheckedChange = { newChecked -> // 等效于 onValueChange
isChecked = newChecked
}
)
- 注意:开关类组件通常使用 onCheckedChange,但本质与 onValueChange 相同。
2.与传统 Android View 的区别
|------|-------------------------|-------------------------|
| 特性 | Compose (onValueChange) | Android View 系统 |
| 触发时机 | 即时(每字符/每帧变化) | 可能延迟(如TextWatcher需手动监听) |
| 状态管理 | 必须显式更新状态 | 自动更新内部状态(需手动同步) |
| 性能优化 | 自动跳过不必要的重组 | 需手动优化invalidate() |
- 声明式 UI 原则 :UI 是状态的函数,onValueChange 强制开发者显式管理状态变化,确保数据流可预测。
- 解耦与复用:通过将状态提升到父组件,子组件(如 TextField)变为无状态,更容易复用和测试。
- 组合优于继承:回调函数作为参数传入,而非通过继承/接口实现,更符合 Compose 的组合式设计。
3.高级用法示例
3.1 输入验证
Kotlin
onValueChange = { newText ->
if (newText.length <= 10) { // 限制最大长度
text = newText
}
}
3.2 格式化输入
Kotlin
onValueChange = { newText ->
if (newText.matches(Regex("^\\d+\$"))) { // 只允许数字
text = newText
}
}
3.3 与 ViewModel 交互
Kotlin
val viewModel: MyViewModel = viewModel()
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { newText ->
text = newText
viewModel.onSearchTextChanged(newText) // 通知 ViewModel
}
)
4.常见问题
- 为什么直接修改状态而不调用 onValueChange 不会更新 UI?
因为 Compose 的响应式机制依赖于状态对象的读取跟踪。只有通过 onValueChange 修改状态,Compose 才能知道需要重组哪些组件。
- 能否省略 onValueChange?
不能,如果省略,组件会变成只读的(用户操作无法修改其值)。例如,无 onValueChange 的 TextField 会无法输入文字。
总结
- onValueChange是 Compose 中处理用户输入值变化的标准回调。
- 必须在回调中更新状态变量(如 mutableStateOf),否则 UI 无响应。
- 遵循单向数据流,支持输入验证、防抖等高级场景。
- 设计哲学:显式状态管理 + 声明式 UI = 高效且可维护的代码。
五、主题配置
传统的主题配置方式繁琐复杂,开发者几乎都不愿意去做这件事情,而compose提供了更加先进的主题配置方式,可以让我们更方便的统一管理主题样式。
Kotlin
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
//夜间模式-颜色方案
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
//亮色颜色方案
private val LightColorScheme = lightColorScheme(
primary = Purple40,//再次一级(第三级)
secondary = PurpleGrey40,//次色调
tertiary = Pink40,//主色调
background = Color.White, // 背景色
surface = Color.White, // 表面色
onPrimary = Color.White, // 主色上的文字颜色
onSecondary = Color.Black, // 次色上的文字颜色
onBackground = Color.Black, // 背景色上的文字颜色
onSurface = Color.Black, // 表面色上的文字颜色
error = Color.Red, // 错误颜色
onError = Color.White, // 错误颜色上的文字颜色
)
/**
* 定义一个主题
*/
@Composable
fun MyComposeApplicationTheme(
//控制当前是否使用暗色主题,默认值为 isSystemInDarkTheme()作用是检测系统是否处于暗黑模式
darkTheme: Boolean = isSystemInDarkTheme(),
//是否启用动态主题 Android 12+ 的设备可以从壁纸提取主题色,
dynamicColor: Boolean = true,
//子 Composable 内容
content: @Composable () -> Unit
) {
//根据条件分支返回一个颜色主题
val colorScheme = when {
//如果设备是 Android 12+(API 31+)且 dynamicColor=true
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current//获取当前上下文
// 使用 dynamicDarkColorScheme 或 dynamicLightColorScheme(根据 darkTheme 选择)。
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
//如果不使用动态主题,则根据 darkTheme 选择预定义的 DarkColorScheme 或 LightColorScheme。
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
//获取当前 Compose 对应的 Android View
val view = LocalView.current
//避免在 Android Studio 预览时执行
if (!view.isInEditMode) {
//在每次成功重组后执行,确保副作用不会重复触发
SideEffect {
val window = (view.context as Activity).window
//将状态栏颜色设置为 colorScheme.primary主题色
window.statusBarColor = colorScheme.primary.toArgb()
//通过 WindowCompat 控制状态栏图标颜色(暗色主题时图标应为浅色,反之亦然)
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
//应用定义的主题到子 Composable
MaterialTheme(
colorScheme = colorScheme,//颜色
typography = Typography,//字体样式
content = content//子组件树,会自动继承主题配置
)
}