Jetpack Compose 进阶:动画、手势、主题与导航
各位同学,欢迎来到第24章。前面我们聊了Compose的基础组件和布局,今天要讲的东西,才是真正让App“活”起来的关键。动画、手势、主题、导航,再加上ViewModel集成——说白了,就是让你的界面会动、会响应、会统一风格,还能在不同页面间优雅跳转。
我个人习惯把这一章称为“Compose的成人礼”。因为一旦你掌握了这些,你就不再是只会堆组件的初级开发者了。来,我们一个一个看。
1. Compose 动画:让界面“活”起来
Compose的动画系统,跟传统View动画完全是两码事。它基于状态驱动,你只需要告诉系统“我要从A变到B”,剩下的插值、刷新,框架全包了。
核心API就三个:
animateFloatAsState—— 单个数值动画,比如透明度、旋转角度animateColorAsState—— 颜色过渡动画AnimatedVisibility—— 进场/退场动画
我在项目中遇到过最典型的场景:点赞按钮的缩放效果。用户点一下,图标弹一下,反馈感瞬间拉满。
@Composable
fun LikeButton(isLiked: Boolean, onClick: () -> Unit) {
val scale by animateFloatAsState(
targetValue = if (isLiked) 1.2f else 1.0f,
animationSpec = spring(dampingRatio = 0.5f)
)
IconButton(onClick = onClick) {
Icon(
imageVector = if (isLiked) Icons.Filled.Favorite else Icons.Outlined.Favorite,
contentDescription = "点赞",
modifier = Modifier.scale(scale),
tint = if (isLiked) Color.Red else Color.Gray
)
}
}
spring() 做弹性动画,手感比线性动画好太多。用户会觉得你的App“很跟手”。
2. 手势处理:从点击到拖拽
Compose的手势系统,我刚开始也觉得有点绕。但用熟了你会发现,它比传统View的OnTouchListener清晰得多。
常用手势修饰符:
clickable—— 点击,最简单draggable—— 单方向拖拽pointerInput—— 自定义手势,比如双指缩放
举个例子,实现一个可拖拽的卡片:
@Composable
fun DraggableCard() {
var offsetX by remember { mutableStateOf(0f) }
Box(
modifier = Modifier
.offset { IntOffset(offsetX.roundToInt(), 0) }
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offsetX += dragAmount.x
}
}
.size(100.dp)
.background(Color.Blue)
)
}
pointerInput 里忘记调用 change.consume(),结果手势事件被多个组件同时响应,界面乱跳。记住:消费事件是必须的。
3. 主题与 Material You:让App有“灵魂”
Material You 是Google在Android 12推出的设计语言。说白了,就是让系统根据壁纸颜色自动生成一套主题色。Compose天然支持这套机制。
核心思路: 定义 MaterialTheme 的颜色、字体、形状三个维度。
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
secondary = Color(0xFF625B71),
tertiary = Color(0xFF7D5260)
)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = LightColorScheme,
typography = Typography,
shapes = Shapes,
content = content
)
}
我个人习惯把主题配置单独放在一个 Theme.kt 文件里。这样换肤、适配深色模式都方便。你想想看,如果颜色散落在各个组件里,改起来得多痛苦?
4. Compose Navigation:页面跳转的新方式
Compose Navigation 跟传统Fragment Navigation最大的区别:它没有Fragment了。每个页面就是一个Composable函数,通过NavHost来管理。
基本三步走:
- 定义路由(字符串常量)
- 创建NavHost,注册页面
- 用navController.navigate()跳转
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
}
detail/{itemId}),不要用序列化对象。复杂数据建议通过ViewModel共享。
5. Compose 与 ViewModel 集成:状态管理的“黄金搭档”
ViewModel在Compose里的用法,跟传统View层不太一样。核心变化是:你不再需要LiveData,直接用 StateFlow 或 MutableState。
推荐模式:
- ViewModel 内部用
MutableState或StateFlow - Composable 里用
viewModel()获取实例 - 用
collectAsState()或by mutableStateOf监听变化
class MyViewModel : ViewModel() {
var count by mutableStateOf(0)
private set
fun increment() { count++ }
}
@Composable
fun CounterScreen(viewModel: MyViewModel = viewModel()) {
Column {
Text("计数: ${viewModel.count}")
Button(onClick = { viewModel.increment() }) {
Text("+1")
}
}
}
嗯,这里要注意:viewModel() 默认会跟随最近的NavBackStackEntry的生命周期。如果你在嵌套导航里用,记得指定 viewModelStoreOwner,否则可能会拿到错误的实例。
6. 综合实战:一个带动画的待办事项列表
最后,我们把今天学的串起来。做一个简单的待办列表:点击添加按钮弹出输入框(带动画),长按删除(手势),主题跟随系统(Material You),页面跳转到详情(Navigation),数据存在ViewModel里。
// 伪代码示意
@Composable
fun TodoApp() {
val navController = rememberNavController()
MaterialTheme {
NavHost(navController, "list") {
composable("list") { TodoListScreen(navController) }
composable("detail/{id}") { TodoDetailScreen() }
}
}
}
你看,所有知识点都融到一起了。这就是Compose的魅力——它不是一个一个孤立的API,而是一套完整的声明式UI生态。
好了,这一章的内容就到这里。代码不多,但每个点都值得你亲手敲一遍。尤其是动画和手势,光看是学不会的,得动手指。