Jetpack Compose中Composable的进出动画实现咨询
一、初始入场动画实现
Activity启动时的元素入场动画,无需依赖AnimatedContent(它更适配内容切换场景),用animateOffsetAsState或AnimatedVisibility即可快速实现:
1. 居中图片从上滑入
通过状态控制偏移量,让图片从上方初始位置滑至屏幕中心:
val isEntered by remember { mutableStateOf(false) } val imageOffsetY by animateOffsetAsState( targetValue = if (isEntered) Offset(0f, 0f) else Offset(0f, -200.dp.toPx()), animationSpec = tween(500, easing = FastOutSlowInEasing) ) Image( painter = painterResource(id = R.drawable.center_img), contentDescription = "Center Image", modifier = Modifier .offset { IntOffset(0, imageOffsetY.y.roundToInt()) } .align(Alignment.Center) ) // 在LaunchedEffect中触发入场 LaunchedEffect(Unit) { delay(100) // 可选延迟优化启动体验 isEntered = true }
2. 底部按钮从下滑入
同理,控制按钮的垂直偏移量实现入场:
val buttonOffsetY by animateOffsetAsState( targetValue = if (isEntered) Offset(0f, 0f) else Offset(0f, 100.dp.toPx()), animationSpec = tween(500, easing = FastOutSlowInEasing) ) Row( modifier = Modifier .fillMaxWidth() .offset { IntOffset(0, buttonOffsetY.y.roundToInt()) } .padding(bottom = 32.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { Button(onClick = { /* 取消逻辑 */ }) { Text("Cancel") } Button(onClick = { /* 切换页面逻辑 */ }) { Text("Continue") } }
二、页面切换动画(点击Continue)
这里AnimatedContent完全适用,它专门处理内容切换过渡,且支持自定义动画逻辑,方便快速切换效果:
1. 基础切换实现
用状态变量控制当前显示页面,AnimatedContent自动处理过渡:
enum class Screen { First, Second } var currentScreen by remember { mutableStateOf(Screen.First) } AnimatedContent( targetState = currentScreen, transitionSpec = { // 旧内容上滑退场,新内容下滑入场 slideOutVertically(targetOffsetY = { -it }) + fadeOut() with slideInVertically(initialOffsetY = { it }) + fadeIn() } ) { screen -> when (screen) { Screen.First -> FirstScreen(onContinueClick = { currentScreen = Screen.Second }) Screen.Second -> SecondScreen(onBackClick = { currentScreen = Screen.First }) } }
2. 便捷切换动画效果
将过渡动画封装为可复用函数,需要切换时直接替换:
// 垂直滑动过渡 fun verticalSlideTransition(): ContentTransform { return slideOutVertically(targetOffsetY = { -it }) + fadeOut() with slideInVertically(initialOffsetY = { it }) + fadeIn() } // 水平滑动过渡 fun horizontalSlideTransition(): ContentTransform { return slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() with slideInHorizontally(initialOffsetX = { it }) + fadeIn() } // 使用时替换transitionSpec即可 AnimatedContent( targetState = currentScreen, transitionSpec = { horizontalSlideTransition() } ) { /* 页面内容 */ }
三、关于AnimatedContentScope的说明
AnimatedContentScope是AnimatedContent的内部作用域,提供animate*系列函数(如animateFloat、animateOffset)和initialSize、targetSize等属性,用于精细化控制过渡。比如结合大小变化的动画:
AnimatedContent(targetState = currentScreen) { screen -> val scale by animateFloatAsState( targetValue = if (targetState == screen) 1f else 0.8f, animationSpec = tween(300) ) when (screen) { Screen.First -> FirstScreen( modifier = Modifier.scale(scale), onContinueClick = { currentScreen = Screen.Second } ) // ... } }
附:《自定义Jetpack Compose中的AnimatedContent》译文
自定义Jetpack Compose中的AnimatedContent
Jetpack Compose的AnimatedContent是处理内容切换动画的核心工具,但默认过渡效果未必能满足所有场景需求。本文将介绍如何自定义AnimatedContent的过渡逻辑,实现更个性化的视觉效果。
1. 理解ContentTransform
ContentTransform定义了内容退场与入场的动画组合,由initialContentExit(旧内容退场动画)和targetContentEnter(新内容入场动画)两部分组成。你可以用+组合多种动画,比如滑动+淡入淡出:
val transition = slideInHorizontally(initialOffsetX = { it }) + fadeIn()
2. 利用AnimatedContentScope的属性
在AnimatedContent的内容Lambda中,可访问AnimatedContentScope的属性(如initialSize旧内容尺寸、targetSize新内容尺寸),实现基于尺寸变化的动画:
AnimatedContent(targetState = isExpanded) { expanded -> val height by animateDpAsState( targetValue = if (expanded) 200.dp else 50.dp, animationSpec = spring() ) Box(modifier = Modifier.height(height).fillMaxWidth().background(Color.Gray)) }
3. 封装自定义过渡动画
将复杂动画逻辑封装为可复用函数,方便在多处调用:
fun expandCollapseTransition(): ContentTransform { return fadeOut(tween(150)) with fadeIn(tween(150)) using SizeTransform(clip = false) { initialSize, targetSize -> if (targetSize.height > initialSize.height) { keyframes { durationMillis = 300 initialSize at 0 with LinearOutSlowInEasing targetSize at 300 with FastOutLinearInEasing } } else { keyframes { durationMillis = 300 initialSize at 0 with FastOutLinearInEasing targetSize at 300 with LinearOutSlowInEasing } } } }
4. 处理复杂内容切换
当切换内容结构差异较大时,可通过contentKey指定唯一标识确保动画触发正确,同时结合rememberUpdatedState避免不必要的重组:
AnimatedContent( targetState = selectedItem, contentKey = { it.id }, transitionSpec = { expandCollapseTransition() } ) { item -> val currentItem by rememberUpdatedState(item) ItemDetail(item = currentItem) }
通过这些技巧,你可以完全掌控AnimatedContent的动画效果,为应用添加更流畅、更具特色的过渡体验。
内容的提问来源于stack exchange,提问作者StuartDTO

