关于compose动画的文章
fun捞-# 玩转 Compose 动画:手把手教你实现微博点赞彩虹特效
概述
compose 为支持动画提供了大量的 api,通过这些 api 我们可以轻松实现动画效果
ps:这些 api 的原理与 Flutter 很接近,与原生的 api 相去甚远
你可以提前看看用 compose 实现的一个放大缩小动画,总的来说还是比较流畅:
低级别动画 API
animate*AsState
所能处理属性的种类:Float、Color、Dp、Size、Bounds、Offset、Rect、Int、IntOffset 和 IntSize
通过 animate*AsState 我们可以实现单一属性的动画效果,我们只需要提供目标值就可以自动从当前进度动画过渡到目标值
实现放大动画
- 代码
@Composable fun animSize() { val enable = remember { mutableStateOf(true) } val size = animateSizeAsState(targetValue = if (enable.value) Size(50f, 50f) else Size(300f, 300f)) Column( modifier = Modifier.fillMaxSize(1f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image( modifier = Modifier .size(size.value.width.dp, size.value.height.dp) .clickable { enable.value = !enable.value }, painter = painterResource(id = R.drawable.apple), contentDescription = "" ) } } 复制代码
- 实现效果
实现颜色变化动画
- 代码
@Composable fun animColor() { val enable = remember { mutableStateOf(true) } val colors = animateColorAsState(targetValue = if (enable.value) Color.Green else Color.Red) val size = animateIntSizeAsState( targetValue = if (enable.value) IntSize(100, 100) else IntSize( 300, 300 ) ) Column( modifier = Modifier.fillMaxWidth(1f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Box( modifier = Modifier .size(size.value.width.dp, size.value.height.dp) .height(400.dp) .background( color = colors.value, shape = if (enable.value) RectangleShape else CircleShape ) ) { } } } 复制代码
- 效果
使用 Animatable 实现颜色变化效果
Animatable 是一个值容器,我们可以通过调用 animateTo 实现动画效果。动画执行过程中如果再次开启动画会中断当前动画。
Animatable 动画执行过程中值的变化是在协程中执行的,所以 animateTo 是一个挂起操作
- 代码
@Composable fun animChangeColor() { val color = remember { Animatable(Color.Red) } val state = remember { mutableStateOf(true) } LaunchedEffect(state.value) { color.animateTo(if (state.value) Color.Red else Color.Magenta) } Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.Center) { Box( modifier = Modifier .background(color.value, shape = RoundedCornerShape(30.dp)) .size(200.dp) .clickable { state.value = !state.value }, contentAlignment = Alignment.Center ) { Text( text = "颜色动画", style = TextStyle(color = Color.White, fontSize = 40.sp) ) } } } 复制代码
- 效果
使用 updateTransition 实现颜色和圆角动画
使用 updateTransition 可以实现多个动画组合的效果。
例如:我们可以在动画执行过程中同时执行大小和颜色变化效果
本例中我们定义了一个枚举用来控制动画,枚举可以定义多个,分别用来对应动画的多个状态
- 代码
@Composable fun animupdateTransition() { var state by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(targetState = state, label = "") val round = transition.animateDp(label = "") { when (it) { BoxState.Collapsed -> 40.dp BoxState.Expanded -> 100.dp } } val color = transition.animateColor(label = "") { when (it) { BoxState.Collapsed -> Color.Red BoxState.Expanded -> Color.Green } } Box(Modifier.fillMaxSize(1f),contentAlignment = Alignment.Center) { Box( modifier = Modifier .size(300.dp) .background( color.value, shape = RoundedCornerShape(corner = CornerSize(round.value)) ) .clickable { state = if (state == BoxState.Collapsed) BoxState.Expanded else BoxState.Collapsed },contentAlignment = Alignment.Center ) { Text(text = "点击开始动画",style = TextStyle(color = Color.White,fontSize = 20.sp)) } } } private enum class BoxState { Collapsed, Expanded } 复制代码
- 效果
rememberInfiniteTransition
rememberInfiniteTransition 的使用和 updateTransition 基本一样,不同的是 rememberInfiniteTransition 的动画一旦开始便会一直反复运行下去,只有被移除动画才能结束
- 代码
@Composable fun rememberInfiniteTransition1() { val infiniteTransition = rememberInfiniteTransition() val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ) ) Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.Center) { Box( Modifier .fillMaxSize(0.8f) .background(color), contentAlignment = Alignment.Center ) { Text( text = "公众号:安安安安卓 原创,禁抄袭", style = TextStyle(color = Color.White, fontSize = 30.sp) ) } } } 复制代码
- 效果
TargetBasedAnimation
TargetBasedAnimation 可以控制动画的执行时间,还可以延迟一段时间再开启动画。
- 代码
@Composable fun animTargetBasedAnimation() { var state by remember { mutableStateOf(0) } val anim = remember { TargetBasedAnimation( animationSpec = tween(2000), typeConverter = Float.VectorConverter, initialValue = 100f, targetValue = 300f ) } var playTime by remember { mutableStateOf(0L) } var animationValue by remember { mutableStateOf(0) } LaunchedEffect(state) { val startTime = withFrameNanos { it } println("进入协程:") do { playTime = withFrameNanos { it } - startTime animationValue = anim.getValueFromNanos(playTime).toInt() } while (!anim.isFinishedFromNanos(playTime)) } Box(modifier = Modifier.fillMaxSize(1f),contentAlignment = Alignment.Center) { Box(modifier = Modifier .size(animationValue.dp) .background(Color.Red,shape = RoundedCornerShape(animationValue/5)) .clickable { state++ },contentAlignment = Alignment.Center) { Text(text = animationValue.toString(),style = TextStyle(color = Color.White,fontSize = (animationValue/5).sp)) } } } 复制代码
- 效果
自定义动画
AnimationSpec
AnimationSpec 可以自定义动画的行为,效果类似于原生动画中的估值器。
SpringSpec 弹簧效果
- 代码
@Composable fun animSpring() { val state = remember { mutableStateOf(true) } var value = animateIntAsState( targetValue = if (state.value) 300 else 100, animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessVeryLow ) ) Box( Modifier .fillMaxSize(1f) .padding(start = 30.dp), contentAlignment = Alignment.CenterStart ) { Box( Modifier .width(value.value.dp) .height(80.dp) .background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp)) .clickable { state.value = !state.value }, contentAlignment = Alignment.CenterStart ) { Text(text = "哈哈哈", style = TextStyle(color = Color.White, fontSize = 20.sp)) } } } 复制代码
- 效果
TweenSpec 动画时间可控
- 代码
@Composable fun animTweenSpec() { val state = remember { mutableStateOf(true) } val value = animateIntAsState( targetValue = if (state.value) 300 else 100, animationSpec = tween( durationMillis = 1500, delayMillis = 200, easing = LinearEasing ) ) Box( Modifier .fillMaxSize(1f) .padding(start = 50.dp), contentAlignment = Alignment.CenterStart ) { Box( Modifier .width(value.value.dp) .height(100.dp) .background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp)) .clickable { state.value = !state.value } ) { } } } 复制代码
- 效果
FrameSpec
- 代码
@Composable fun animkeyframesSpec() { var state by remember { mutableStateOf(true) } val value by animateIntAsState( targetValue = if (state) 300 else 100, animationSpec = keyframes { durationMillis = 2000 0 at 700 with LinearOutSlowInEasing 700 at 1400 with FastOutLinearInEasing 1400 at 2000 }) Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.CenterStart) { Box( Modifier .width(value.dp) .height(100.dp) .background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp)) .clickable { state = !state } ) { } } } 复制代码
- 效果
RepeatableSpec 实现有限次数的重复动画
执行有限次数动画后自动停止
- 代码
@Composable fun animrepeatableSpec() { var state by remember { mutableStateOf(true) } val value by animateIntAsState( targetValue = if (state) 300 else 100, animationSpec = repeatable( iterations = 5,//动画重复执行的次数,设置多少就执行多少次 animation = tween(durationMillis = 1000), repeatMode = RepeatMode.Reverse ) ) Box( Modifier .fillMaxSize(1f) .padding(start = 30.dp), contentAlignment = Alignment.CenterStart) { Box( Modifier .width(value.dp) .height(100.dp) .background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp)) .clickable { state = !state } ) { } } } 复制代码
- 效果
代码中设置了重复 5 次,所以反复执行五次后动画结束
InfiniteRepeatableSpec 无限次数执行动画
动画会无限次的执行下去,直到视图被移除
- 代码
@Composable fun animinfiniteRepeatableSpec() { var state by remember { mutableStateOf(true) } val value by animateIntAsState( targetValue = if (state) 300 else 100, animationSpec = infiniteRepeatable( animation = tween(durationMillis = 1000), repeatMode = RepeatMode.Reverse ) ) Box( Modifier .fillMaxSize(1f) .padding(start = 30.dp), contentAlignment = Alignment.CenterStart) { Box( Modifier .width(value.dp) .height(100.dp) .background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp)) .clickable { state = !state } ) { Text(text = "公众号:安安安安卓 原创,禁转载") } } } 复制代码
- 效果
Easing
Easing 类似于我们原生动画中的差值器
有以下几种选择:
- FastOutSlowInEasing
- LinearOutSlowInEasing
- FastOutLinearInEasing
- LinearEasing
- CubicBezierEasing
这几种实现的效果和 android 原生实现的动画差值器差距很大,甚至看不出有啥效果,所以代码我就不放了。有清楚原因的读者可以联系我
实现效果:
AnimationVector
大多数 Compose 动画 API 都支持将 Float、Color、Dp 以及其他基本数据类型作为开箱即用的动画值,但有时我们需要为其他数据类型(包括我们的自定义类型)添加动画效果
本例中实现颜色和大小的变换动画
代码中我们定义了一个 AnimSize 类,类中的第一个参数是颜色数据,第二个参数是尺寸数据。动画执行过程中会同事改变颜色和控件尺寸效果。
- 代码
@Composable fun animAnimationVector() { var state by remember { mutableStateOf(true) } val value by animateValueAsState( targetValue = if (state) AnimSize(0xffff5500, 100f) else AnimSize(0xff00ff00, 300f), typeConverter = TwoWayConverter( convertToVector = { // AnimationVector2D(target.color.toFloat(), target.size) AnimationVector2D(it.color.toFloat(), it.size) }, convertFromVector = { AnimSize(it.v1.toLong(), it.v2) } ) ) println("颜色:${value.color}") Box(modifier = Modifier.fillMaxSize(1f).padding(30.dp), contentAlignment = Alignment.Center) { Box( modifier = Modifier .size(value.size.dp) // .size(300.dp) .background(Color(value.color), RoundedCornerShape(30.dp)) .clickable { state = !state } ) { } } } data class AnimSize(val color: Long, val size: Float) 复制代码
- 效果
缺点是执行颜色变化过程中有闪烁
高级动画
高级动画一般指封装性较高的动画,使用较为简单,主要有以下三种:
因高级动画效果不明显,gif 很难展现出效果,所以这里不放代码和效果图了
- AnimatedVisibility
- animateContentSize
- Crossfade