Appearance
Jetpack Compose
字数统计:约 15000 字
难度等级:⭐⭐⭐⭐⭐
面试重要度:⭐⭐⭐⭐⭐
目录
1. Compose 简介
1.1 什么是 Jetpack Compose
Jetpack Compose 是 Android 的现代化 UI 工具包:
- 声明式 UI(不同于命令式 View 系统)
- Kotlin 优先
- 无需 XML 布局
- 与现有 View 系统集成1.2 与传统 View 的对比
| 特性 | View 系统 | Compose | |-----|-|-|-| | 声明方式 | XML | Kotlin 代码 | | UI 更新 | 手动刷新 | 自动重组 | | 代码量 | 多 | 少 | | 学习曲线 | 平缓 | 较陡 | | 性能 | 优化充分 | 持续优化 | | 推荐 | 传统项目 | 新项目 |
1.3 集成依赖
gradle
// 启用 Compose
android {
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
}
dependencies {
// Compose 核心
implementation platform("androidx.compose:compose-bom:2023.10.00")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
// Material Design
implementation("androidx.compose.material3:material3")
// Navigation
implementation("androidx.navigation:navigation-compose:2.7.5")
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
// 与 View 系统集成
implementation("androidx.compose.ui:ui-viewbinding")
}2. 核心概念
2.1 @Composable 函数
kotlin
// 声明式 UI
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
// 组合多个 Composable
@Composable
fun UserProfile(user: User) {
Column {
Image(
bitmap = user.avatar,
contentDescription = "Profile picture"
)
Text(text = user.name)
Text(text = user.email)
Button(onClick = { /* click handler */ }) {
Text(text = "Follow")
}
}
}
// 带条件的 Composable
@Composable
fun LoadingOrContent(isLoading: Boolean, content: @Composable () -> Unit) {
if (isLoading) {
CircularProgressIndicator()
} else {
content()
}
}2.2 状态与重组
kotlin
@Composable
fun Counter() {
// 声明状态变量
var count by remember { mutableStateOf(0) }
// 当 count 变化时,自动重组
Column {
Text(text = "Count: $count")
Button(onClick = { count++ }) {
Text(text = "Increment")
}
}
}
// 状态提升
@Composable
fun CounterWithReset() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onReset = { count = 0 })
}
@Composable
fun Counter(count: Int, onReset: () -> Unit) {
Column {
Text(text = "Count: $count")
Button(onClick = onReset) {
Text(text = "Reset")
}
}
}2.3 记忆化
kotlin
// remember - 在重组时保留值
@Composable
fun MyComposable() {
val value = remember { expensiveCalculation() }
// value 在重组时不会重新计算
}
// rememberUpdatedState - 更新状态引用
@Composable
fun MyComposable() {
var count by remember { mutableStateOf(0) }
val latestCount = rememberUpdatedState(count)
LaunchedEffect(Unit) {
// 异步操作中使用最新状态
delay(1000)
// 使用 latestCount 而不是 count
}
}
// derivedStateOf - 派生状态
@Composable
fun MyComposable() {
var name by remember { mutableStateOf("") }
val isNameValid = remember(name) {
name.isNotBlank() && name.length >= 3
}
// 或使用 derivedStateOf
val isNameValid = derivedStateOf {
name.isNotBlank() && name.length >= 3
}.value
}3. 基础使用
3.1 基础组件
kotlin
// Text - 文本
Text(
text = "Hello World",
fontSize = 16.sp,
color = Color.Blue,
fontStyle = FontStyle.Italic
)
// Button - 按钮
Button(
onClick = { /* action */ },
enabled = true,
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.LightGray
)
) {
Text("Click me")
}
// TextField - 输入框
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter text") },
modifier = Modifier.fillMaxWidth()
)
// Image - 图片
Image(
painter = painterResource(id = R.drawable.ic_launcher),
contentDescription = "Logo",
modifier = Modifier.size(48.dp)
)
// 或使用图片加载库
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier.size(100.dp),
contentScale = ContentScale.Crop,
placeholder = ImagePainter(...),
error = ImagePainter(...)
)3.2 布局组件
kotlin
// Row - 水平布局
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text("Left")
Text("Center")
Text("Right")
}
// Column - 垂直布局
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("Top")
Text("Middle")
Text("Bottom")
}
// Spacer - 空白
Spacer(modifier = Modifier.height(16.dp))
// Box - 重叠布局
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.Center
) {
Image(...)
Text("Overlay", modifier = Modifier.padding(8.dp))
}
// Surface - 容器
Surface(
shape = RoundedCornerShape(16.dp),
shadowElevation = 8.dp,
color = Color.White
) {
Text("Content")
}3.3 Modifier 修饰符
kotlin
// 基础 Modifier
Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(16.dp)
.background(Color.Gray)
.clickable { onClick() }
.border(width = 2.dp, color = Color.Black)
.size(100.dp)
.weight(1f)
.heightIn(min = 50.dp)
.widthIn(max = 200.dp)
// 组合 Modifier
val customModifier = Modifier
.padding(16.dp)
.background(Color.White)
.elevation(4.dp)
// 可复用 Modifier
val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.White)
.elevation(8.dp)
.clip(RoundedCornerShape(8.dp))
// 使用
Surface(modifier = cardModifier) {
Text("Card Content")
}
// 顺序重要
// ✅ 正确
Modifier.padding(16.dp).background(Color.Red)
// ❌ 错误(背景在 padding 外部)
Modifier.background(Color.Red).padding(16.dp)4. 状态管理
4.1 StateFlow 与 ViewModel
kotlin
// ViewModel
class LoginViewModel : ViewModel() {
private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
val loginState: StateFlow<LoginState> = _loginState.asStateFlow()
fun login(username: String, password: String) {
viewModelScope.launch {
_loginState.value = LoginState.Loading
try {
val user = repository.login(username, password)
_loginState.value = LoginState.Success(user)
} catch (e: Exception) {
_loginState.value = LoginState.Error(e.message)
}
}
}
}
sealed class LoginState {
object Idle : LoginState()
object Loading : LoginState()
data class Success(val user: User) : LoginState()
data class Error(val message: String?) : LoginState()
}
// Composable 使用
@Composable
fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
val loginState by viewModel.loginState.collectAsState()
LoginUI(state = loginState)
}
@Composable
fun LoginUI(state: LoginState) {
when (state) {
is LoginState.Idle -> LoginForm()
is LoginState.Loading -> LoadingDialog()
is LoginState.Success -> SuccessScreen(state.user)
is LoginState.Error -> ErrorScreen(state.message)
}
}4.2 LaunchedEffect
kotlin
// 在重组时启动协程
@Composable
fun MyComposable() {
LaunchedEffect(Unit) {
// 只在第一次进入时执行
loadData()
}
}
// 带键值的 LaunchedEffect
@Composable
fun MyComposable(itemId: Int) {
LaunchedEffect(itemId) {
// 当 itemId 变化时重新执行
loadItem(item)
}
}
// 清理已启动的协程
@Composable
fun MyComposable() {
val job = remember { Job() }
LaunchedEffect(job) {
while (true) {
delay(1000)
// 定期执行
}
}
DisposableEffect(Unit) {
onDispose {
job.cancel()
}
}
}4.3 SideEffect
kotlin
// SideEffect - 在重组时执行副作用
@Composable
fun MyComposable() {
SideEffect {
// 每次重组时执行
updateAnalytics()
}
}
// 使用场景:
// - 分析埋点
// - 更新全局状态
// - 清理资源4.4 rememberSaveable
kotlin
// 保存状态(屏幕旋转等)
@Composable
fun MyComposable() {
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it }
)
}
// 自定义序列化
@Composable
fun MyComposable() {
var item by rememberSaveable(
saver = UserSaver
) { mutableStateOf(User(1, "Name")) }
}
object UserSaver : Saver<User, Map<String, Any>> {
override fun restore(value: Map<String, Any>): User {
return User(
id = value["id"] as Int,
name = value["name"] as String
)
}
override fun save(value: User): Map<String, Any> {
return mapOf(
"id" to value.id,
"name" to value.name
)
}
}5. 列表与导航
5.1 LazyColumn
kotlin
@Composable
fun UserList(users: List<User>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
count = 100,
key = { it } // 使用 key 优化
) { index ->
UserItem(user = User(index, "User $index"))
}
}
}
// LazyColumn 高级用法
@Composable
fun AdvancedLazyColumn() {
LazyColumn {
// 头部
header {
HeaderItem()
}
// 项目列表
items(users) { user ->
UserItem(user)
}
// 底部
footer {
FooterItem()
}
// 间隔
item {
Spacer(modifier = Modifier.height(16.dp))
}
// 分页加载
stickyHeader {
SectionHeader()
}
}
}
// LazyRow - 水平列表
@Composable
fun HorizontalList() {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(16.dp)
) {
items(items) { item ->
ItemCard(item)
}
}
}
// 嵌套列表
@Composable
fun NestedList() {
LazyColumn {
items(groups) { group ->
GroupHeader(group.name)
LazyColumn {
items(group.items) { item ->
ItemCard(item)
}
}
}
}
}5.2 Navigation Compose
kotlin
// 定义路由
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{id}") {
fun createRoute(id: String) = "detail/$id"
}
object Settings : Screen("settings")
}
// Navigation 图
@Composable
fun AppNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) {
HomeScreen(
onItemClick = { id ->
navController.navigate(Screen.Detail.createRoute(id))
}
)
}
composable(
route = Screen.Detail.route,
arguments = listOf(
navArgument("id") {
type = NavType.StringType
}
)
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")
DetailScreen(
itemId = id,
onBack = { navController.popBackStack() }
)
}
composable(Screen.Settings.route) {
SettingsScreen(
onBack = { navController.popBackStack() }
)
}
}
}
// 深度链接
composable(
route = "profile/{userId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/profile/{userId}"
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId)
}5.3 回退栈处理
kotlin
@Composable
fun MyScreen(navController: NavController) {
// 处理返回按钮
val canPop = remember { navController.previousBackStackEntry != null }
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
if (canPop) {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.Default.ArrowBack, "Back")
}
}
},
title = { Text("Title") }
)
}
) {
// Content
}
}6. 布局与动画
6.1 自定义布局
kotlin
// Layout - 自定义布局
@Layout
fun CustomLayout(
content: @Composable () -> Unit
) {
// 测量子组件
val placeables = content.measure(MeasurableMeasurableConstraints())
layout(width, height) {
// 放置子组件
placeables.forEach { placeable ->
placeable.placeRelative(x, y)
}
}
}
// 使用
CustomLayout {
Box {
Text("Content 1")
Text("Content 2")
}
}6.2 动画
kotlin
// AnimatedVisibility - 显示/隐藏动画
@Composable
fun AnimatedItem(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInHorizontally(),
exit = fadeOut() + slideOutHorizontally()
) {
Card {
Text("Animated Content")
}
}
}
// AnimatedContent - 内容切换动画
@Composable
fun AnimatedContentScreen() {
var count by remember { mutableStateOf(0) }
AnimatedContent(
targetState = count,
transitionSpec = {
slideIntoContainer(
AnimatedContentTransitionSpec.SlideDirection.Left,
200
) with slideOutOfContainer(
AnimatedContentTransitionSpec.SlideDirection.Right,
200
)
}
) { targetCount ->
Text("Count: $targetCount")
}
Button(onClick = { count++ }) {
Text("Increment")
}
}
// animateFloatAsState - 属性动画
@Composable
fun AnimatedRotation(isRotating: Boolean) {
val rotation by animateFloatAsState(
targetValue = if (isRotating) 360f else 0f,
animationSpec = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = null,
modifier = Modifier.rotate(rotation)
)
}
// animateDpAsState
@Composable
fun AnimatedSize(isExpanded: Boolean) {
val height by animateDpAsState(
targetValue = if (isExpanded) 200.dp else 100.dp,
animationSpec = spring()
)
Box(modifier = Modifier.height(height)) {
Text("Animated Size")
}
}6.3 手势
kotlin
// 点击
Modifier.clickable { onClick() }
// 长按
Modifier.longClickable { onLongClick() }
// 双击
Modifier.doubleClickable { onDoubleClick() }
// 拖动
Modifier.draggable(
orientation = Orientation.Vertical,
onDragStopped = { offset -> /* handle */ }
)
// 滚动
LaunchedEffect(Unit) {
Modifier.scrollable(remember { MutableState(0, 0) })
}7. Material Design 3
7.1 主题配置
kotlin
// Material3 主题
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) {
if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
darkDynamicColorScheme()
} else {
darkColorScheme()
}
} else {
if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
lightDynamicColorScheme()
} else {
lightColorScheme()
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
// 自定义颜色
val CustomColorScheme = lightColorScheme(
primary = Purple80,
secondary = Teal200,
tertiary = Pink80,
error = Color(0xFFB3261E)
)
// 自定义字体
val Typography = Typography(
headlineLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
lineHeight = 52.sp,
letterSpacing = (-0.25).sp
),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
)7.2 常用组件
kotlin
// Scaffold - 布局骨架
@Composable
fun AppScreen() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Title") },
actions = {
IconButton(onClick = {}) {
Icon(Icons.Default.Search, null)
}
}
)
},
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(Icons.Default.Home, null) },
label = { Text("Home") }
)
}
},
floatingActionButton = {
FloatingActionButton(onClick = {}) {
Icon(Icons.Default.Add, null)
}
}
) { innerPadding ->
LazyColumn(
contentPadding = innerPadding
) {
items(100) { index ->
CardItem("Item $index")
}
}
}
}
// Card - 卡片
Card(
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Text("Card Content")
}
// Dialog - 对话框
@Composable
fun DialogScreen() {
var showDialog by remember { mutableStateOf(false) }
Button(onClick = { showDialog = true }) {
Text("Show Dialog")
}
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("Dialog Title") },
text = { Text("Dialog Content") },
confirmButton = {
TextButton(onClick = { showDialog = false }) {
Text("OK")
}
}
)
}
}
// Snackbar - 通知
@Composable
fun SnackbarScreen() {
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) {
Button(onClick = {
snackbarHostState.showSnackbar("Action completed")
}) {
Text("Trigger Snackbar")
}
}
}8. 性能优化
8.1 重组优化
kotlin
// 使用 key
@Composable
fun ItemList(items: List<Item>) {
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item)
}
}
}
// 使用 subcomposition
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
SubComposition(count)
// 其他不相关的状态
var other by remember { mutableStateOf("") }
}
// 避免在 Composable 中创建对象
@Composable
fun Inefficient() {
// ❌ 每次都创建新对象
val data = listOf(1, 2, 3)
Text(data.toString())
}
@Composable
fun Efficient() {
// ✅ 使用 remember
val data = remember { listOf(1, 2, 3) }
Text(data.toString())
}8.2 内存优化
kotlin
// 使用 LazyColumn 替代 Column
@Composable
fun InefficientList() {
// ❌ 所有项目都创建
Column {
repeat(1000) {
ItemCard("Item $it")
}
}
}
@Composable
fun EfficientList() {
// ✅ 只创建可见项目
LazyColumn {
items(1000) {
ItemCard("Item $it")
}
}
}
// 图片加载优化
@Composable
fun OptimizedImage(url: String) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(8.dp))
)
}9. 面试考点
9.1 基础概念
Q1: Compose 的优势?
答案要点:
- 声明式 UI(状态驱动)
- 代码量少
- 与 Kotlin 深度集成
- 无需 XML
- 自动处理状态变化
- 更好的测试性Q2: @Composable 是什么?
答案要点:
- 标记 UI 函数的注解
- 支持状态和重组
- 可以组合其他 Composable
- 支持 remember 和状态管理9.2 实战问题
Q3: 如何实现列表?
kotlin
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item)
}
}Q4: 状态如何管理?
kotlin
var count by remember { mutableStateOf(0) }
// 或使用 ViewModel + StateFlow
val state by viewModel.state.collectAsState()9.3 高级问题
Q5: 重组优化技巧?
答案要点:
1. 使用 key 稳定项目
2. 使用 remember
3. 拆分 Composable
4. 使用 derivedStateOf
5. 避免创建对象参考资料
本文完,感谢阅读!