XML
动画还是使用 Animation
类;
@Compose
方法里面,通常只需要确认需要修改的属性(大小、位置、透明度)等,再用合适的动画类型去修改这些属性就可以了。
ScaleAnimation
、 AlphaAnimation
,再用 AnimationSet
来将动画合并;并且还要同时写出 toBigAnimateSet
和 toSmallAnimateSet
,最后“命令”指定的视图去执行这个动画。
enum class HeartState { SMALL, BIG } // 定义红心的两种类型
var action by remember { mutableStateOf(HeartState.SMALL) } // 指定红心状态(也是动画的触发器)
val animationTransition = updateTransition(targetState = action, label = "") // 创建动画类
// 定义不同状态下的尺寸
val size by animationTransition.animateDp(label = "改变大小") { state ->
if (state == HeartState.SMALL) 40.dp else 60.dp
}
// 定义不同状态下的透明度
val alpha by animationTransition.animateFloat(label = "改变透明度") { state ->
if (state == HeartState.SMALL) 0.5f else 1f
}
// 绘制红心
Image(
modifier = Modifier.size(size = size).alpha(alpha = alpha),
painter = painterResource(id = R.drawable.heart),
contentDescription = "heart"
)
// 点击触发状态切换
Text(
text = "切换",
modifier = Modifier
.padding(top = 10.dp, bottom = 50.dp)
.clickable { action = if (action == HeartState.SMALL) HeartState.BIG else HeartState.SMALL }
)
复制代码
AnimatedVisibility
来实现。甚至再加一两行代码,控制出场和退场的方式。var isVisible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = isVisible,
enter = slideInVertically() + fadeIn(), // 水平方向划入 + 渐变展示
exit = slideOutHorizontally() + fadeOut() // 垂直方向划出 + 渐变隐藏
) {
Image(
modifier = Modifier.size(size = 50.dp),
painter = painterResource(id = R.drawable.heart),
contentDescription = "heart"
)
}
复制代码
除了上面提到的
AnimatedVisibility
,Compose还提供了很多封装好的动画,这些 API 经过专门设计,符合 Material Design 运动的最佳做法。接下来,我将会一一介绍。
AnimatedVisibility
动画,现在我们来看下具体使用:传送门@Composable
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
content: @Composable() AnimatedVisibilityScope.() -> Unit
)
复制代码
visible
:动画的触发器。当数值从 false
→ true
时,会执行 enter
动画;相反,会执行 exit
动画;
enter
:对象的进入动画,传入 EnterTransition
的子类。Compose 已经封装好高度易用的动画类,如 fade
、 slide
、 scale
、 expand
等;
exit
:对象的退出动画,传入 ExitTransition
的子类。上述的进入动画均有一一对应的退出动画;
content
:需要执行动画的内容。值得注意的是,当前 content
是定义在 AnimatedVisibilityScope
中的,其中提供了 transition
对象可直接使用,可以理解成时刻同步动画状态的对象,通过 transition
对象,我们可以高度定制化地自定义动画过程中的其他动画。(关于Transition类后面会详细介绍)
AnimatedVisibilityScope
的 transition
来添加自定义动画效果:
例子:在红心出现和消失的同时,我们需要同时改变红心的颜色
var isVisible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = isVisible,
enter = slideInVertically() + fadeIn(),
exit = slideOutHorizontally() + fadeOut()
) {
// 此处 transition 是 AnimatedVisibilityScope 中的成员
val customColor by transition.animateColor(label = "颜色变化") { state ->
if (state == EnterExitState.Visible) Color.Blue else Color.Red
}
Icon(
modifier = Modifier.size(size = 50.dp),
painter = painterResource(id = R.drawable.heart),
tint = customColor,
contentDescription = "heart"
)
}
复制代码
AnimatedVisibility
内子项的进出动画,可以使用 Modifier.animateEnterExit
来重新定制动画;Visible
和 Gone
状态,在视图消失的时候会带来布局容器的改变;AnimatedContent
动画,我们来看下具体的使用:传送门@ExperimentalAnimationApi
@Composable
fun <S> AnimatedContent(
targetState: S,
modifier: Modifier = Modifier,
transitionSpec: AnimatedContentScope<S>.() -> ContentTransform = {
fadeIn(animationSpec = tween(220, delayMillis = 90)) with fadeOut(animationSpec = tween(90))
},
contentAlignment: Alignment = Alignment.TopStart,
content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
)
复制代码
<S>
可以看出这是一个可以适配不同内容类型的泛型方法,可以使用 S
来定义其类型;targetState
:动画的触发器,传入下个阶段的状态。比如内容从 “Hello” 切换到 “world”,“world” 就是此时传入的 targetState
;transitionSpec
:执行动画的规范。当前参数所需要传入的是能返回 ContentTransform
对象的方法; ContentTransform
的生成一般是调用 with
infix方法生成,如下:// ContentTransform 的作用是会记录视图的 进入/退出 动画 infix fun EnterTransition.with(exit: ExitTransition) = ContentTransform(this, exit) 复制代码
AnimatedContentScope<S>.() -> ContentTransform
,指的是在当前方法内,使用者可以根据 AnimatedContentScope
内的对象返回自己需要的动画规范;contentAlignment
:内容的对齐方式。默认会从向着布局的左上方进入和退出;AnimatedContentScope
: transition
对象,还会给调用方提供 initialState
(变化前的状态) 和 targetState
(变化后的状态) ,供我们使用;var count by remember { mutableStateOf(0) } // 初始值为 0
AnimatedContent(
targetState = count,
transitionSpec = {
if (targetState > initialState) {
// 数字变大时,进入的数字从下往上变深划入,退出的数字从下往上变浅划出
slideInVertically({ height -> height }) + fadeIn() with slideOutVertically({ height -> -height }) + fadeOut()
} else {
// 数字变小时,进入的数字从上往下变深划入,退出的数字从上往下变浅划出
slideInVertically({ height -> -height }) + fadeIn() with slideOutVertically({ height -> height }) + fadeOut()
}
}
) { targetCount ->
Text(text = "$targetCount")
}
// 启动协程切换数字,达到每 1 秒自动切换的效果
LaunchedEffect(Unit) {
while (true) {
if (count == 0) count++ else count--
delay(1000)
}
}
复制代码
animateContentSize
(动画效果实现视图的尺寸变化) 和 Crossfade
(淡入淡出效果实现布局切换);using
和 SizeTransform
来定义切换过程中的尺寸变化,此处不作详述。infix fun ContentTransform.using(sizeTransform: SizeTransform?)
复制代码
animate*AsState
是一个非常简单的 API,只需要提供最终值,API 就会从当前值开始播放动画;Float
、 Color
、 Dp
、 Size
、 Offset
、 Rect
、 Int
、 IntOffset
和 IntSize
等基本类型都提供了 animate*AsState
方法,我们举 animateFloatAsState
为例@Composable
fun animateFloatAsState(
targetValue: Float,
animationSpec: AnimationSpec<Float> = defaultAnimation,
visibilityThreshold: Float = 0.01f,
finishedListener: ((Float) -> Unit)? = null
): State<Float>
复制代码
targetValue
:动画的触发器。当这个值发生变化时,就会触发动画的执行;animationSpec
:执行动画的规范。后续会细讲。visibilityThreshold
:判断是否已经靠近目标数值的阈值。finishedListener
:动画结束监听器。updateTransition
。@Composable
fun <T> updateTransition(targetState: T, label: String? = null): Transition<T>
复制代码
targetState
是动画的触发器,其类型可以是我们自定义的不同状态;Transition<T>
对象,而此类型也提供了返回不同类型的 animate*
方法,和上述的 animate*AsState
类似。animate*AsState
还是 Transition.animate*
,我们都会遇到变化的属性为自定义类型(非基本类型) 的情况。Compose 提供了便捷的 API 供我们自定义变化规则。而他就是 TwoWayConverter
;@Composable
fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>,
// ...
): State<T>
@Composable
inline fun <S, T, V : AnimationVector> Transition<S>.animateValue(
typeConverter: TwoWayConverter<T, V>,
// ...
): State<T>
复制代码
convert
的含义是“转换”,而 TwoWayConverter
的作用是定义一个 自定义类型 转成 N个浮点值 来执行动画,再将动画返回的 N个浮点值 转成 自定义类型 的过程。animateRectAsState
的源码,就可以很容易理解上面这句话。// 1.可以看到 animate*AsState 内部执行的都是 animateValueAsState
@Composable
fun animateRectAsState( /* ... */): State<Rect> {
return animateValueAsState(
targetValue, Rect.VectorConverter, animationSpec, finishedListener = finishedListener
)
}
// 2.对于当前方法,传入的 TwoWayConverter 是 Rect.VectorConverter
private val RectToVector: TwoWayConverter<Rect, AnimationVector4D> =
TwoWayConverter(
convertToVector = { AnimationVector4D(it.left, it.top, it.right, it.bottom) },
convertFromVector = { Rect(it.v1, it.v2, it.v3, it.v4) }
)
// 3.TwoWayConverter<T, V>的类型接收两个参数,T指的是原来的类型,V指的是需要中转的类型;
// AnimationVector4D 指的是使用持有 4 个浮点值的 Vector 进行中转;
// 整个过程就是 Rect -> AnimationVector4D -> Rect,非常容易理解。
复制代码
API | 含义 | 属性 |
---|---|---|
spring | 弹窗动画 | dampingRatio:定义弹簧的弹性,可选参数如Spring.DampingRatioHighBouncy;stiffness:定义弹簧向结束值移动的速度,可选参数如 Spring.StiffnessMedium |
tween | 定时动画 | durationMillis:定义动画的持续时间;delayMillis:定义动画开始的延迟时间;easing:定义起始值和结束值之间的动画效果,如LinearOutSlowInEasing |
keyframe | 关键帧动画 | 同 tween |
repeatable | 重复动画 | iterations:迭代次数;animation:需要重复执行的动画;repeatMode:重复的模式,如从头开始 (RepeatMode.Restart) 还是从结尾开始 (RepeatMode.Reverse) |
在一些相同的场景下,对于不同的视图执行的对象是一样的,这时候我们就应该对相同的部分进行抽离。当然,这一切都是有办法的,并不是直接抽函数那么简单。
// 注意这里传入的是 State 对象,这样才能保证视图能够在重组过程中被持续刷新
private class AnimateTransitionData(size: State<Dp>, alpha: State<Float>) {
val size by size // kotlin 的语法糖,同名委托表示取值
val alpha by alpha
}
复制代码
@Composable
private fun updateTransitionData(targetState: HeartState): AnimateTransitionData {
val transition = updateTransition(targetState = targetState, label = "")
// 定义不同状态下的尺寸
val size = transition.animateDp(label = "改变大小") { state ->
if (state == HeartState.SMALL) 40.dp else 60.dp
}
// 定义不同状态下的透明度
val alpha = transition.animateFloat(label = "改变透明度") { state ->
if (state == HeartState.SMALL) 0.5f else 1f
}
// 此处将 transition 对象作为 remember 的入参,表明每当 transitio 更新,都会触发 AnimateTransitionData 的更新和返回
return remember(transition) { AnimateTransitionData(size, alpha) }
}
复制代码
var action by remember { mutableStateOf(HeartState.SMALL) }
val data = updateTransitionData(action)
// 绘制红心
Image(
modifier = Modifier
.size(size = data.size)
.alpha(alpha = data.alpha),
painter = painterResource(id = R.drawable.heart),
contentDescription = "heart"
)
复制代码
AnimatedVisibility
、控制内容变化的 AnimatedContent
和 Crossfade
等;animate*AsState
、控制多状态变化的 updateTransition
等;TwoWayConverter
;本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。