Jetpack Compose实现Box先扩宽再增高显内容的自定义动画求助
Jetpack Compose完全可以实现你描述的顺序展开动画效果。你提到的AnimationVector是Compose动画框架的底层数据类型,用于封装不同维度的动画数值,普通业务开发不需要直接调用,使用官方封装的上层动画API即可满足需求。
需要学习的核心API
animate*AsState系列:你已经在使用的单属性动画API,适合对宽度、高度、透明度等单个属性做目标值切换的过渡动画,使用成本低,适合简单动画场景。Animatable:比animate*AsState更灵活的单属性动画API,支持在协程中控制动画的启动、停止,还可以等待动画执行完成,适合多阶段顺序执行的动画场景,无需硬编码动画延迟时间。TransitionAPI:针对多状态联动的动画场景设计,你可以定义多个业务状态(如隐藏、宽度展开完成、高度展开完成),所有动画的执行时机、时长都和状态绑定,不需要单独维护多个动画状态的同步逻辑。- 透明度动画:通过
animateFloatAsState控制Text的alpha属性即可实现纯淡入效果,避免AnimatedVisibility和父容器裁剪共同作用带来的滑动错觉。
现有代码优化方案
你当前的实现存在三个可优化点:
- 冗余的隐藏文本组件:不需要提前添加不可见的重复文本测量尺寸,给外层容器开启裁剪,文本正常放在容器内,初始宽高设为极小值,动画过程中容器会自动裁剪超出范围的内容即可。
- 硬编码的动画延迟:当前你通过写死延迟时间保证宽度动画结束后再执行高度动画,后续修改动画时长需要同步修改多处配置,容易出错。可以在协程中顺序执行动画,等待宽度动画结束后再启动高度动画。
- 文本淡入异常:你看到文本从底部出现的效果,是因为父容器高度增加过程中,被裁剪的文本下半部分逐渐露出带来的错觉,直接控制Text的alpha属性即可实现纯淡入效果。
优化后的参考代码
@ExperimentalAnimationApi @Composable fun MessageBoxAnimated() { val text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book." val scope = rememberCoroutineScope() val density = LocalDensity.current // 动画状态 val widthAnimatable = remember { Animatable(10.dp, Dp.VectorConverter) } val heightAnimatable = remember { Animatable(10.dp, Dp.VectorConverter) } val textAlphaAnimatable = remember { Animatable(0f) } // 文本最终尺寸,测量一次即可 var textTargetWidth by remember { mutableStateOf<Dp?>(null) } var textTargetHeight by remember { mutableStateOf<Dp?>(null) } Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.padding(20.dp) ) { Column( Modifier .width(widthAnimatable.value) .height(heightAnimatable.value) .background(Color.Gray) .clipToBounds(), // 开启裁剪,超出容器的内容不可见 verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = text, modifier = Modifier .padding(20.dp) .alpha(textAlphaAnimatable.value) .onSizeChanged { // 首次测量拿到文本最终需要的宽高 if (textTargetWidth == null) { with(density) { textTargetWidth = it.width.toDp() + 40.dp // 加上左右padding textTargetHeight = it.height.toDp() + 40.dp // 加上上下padding } } }, color = MaterialTheme.colors.onBackground, fontFamily = FontFamily.SansSerif ) } Button( onClick = { val targetW = textTargetWidth ?: return@Button val targetH = textTargetHeight ?: return@Button scope.launch { // 第一步:执行宽度展开动画,时长400ms widthAnimatable.animateTo( targetValue = targetW, animationSpec = tween( durationMillis = 400, easing = LinearOutSlowInEasing ) ) // 第二步:宽度动画结束后执行高度展开动画,时长400ms launch { heightAnimatable.animateTo( targetValue = targetH, animationSpec = tween( durationMillis = 400, easing = LinearOutSlowInEasing ) ) } // 延迟200ms(高度动画走到一半)开始淡入文本 delay(200) textAlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 200) ) } }, colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue) ) { Text(text = "Press here", color = Color.White) } } }
内容的提问来源于stack exchange,提问作者Barrufet
相关产品推荐
相关产品推荐

