Jetpack Compose——AnimatedVisibility
AnimatedVisibility 用于制作可见性动画。
BoxWithConstraints(Modifier.fillMaxSize()) {
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(visible) {
Image(
painter = painterResource(R.drawable.compose),
modifier = Modifier.size(200.dp),
contentDescription = null
)
}
Button(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(16.dp),
onClick = { visible = !visible }
) {
Text(text = if (visible) "Hide" else "Show")
}
}
使用非常简单,调用 AnimatedVisibility(visable = ...){ ... }
传入一个 Boolean 状态和内容,当状态改变时,content 就会动画显现或消失。
@Composable
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
label: String = "AnimatedVisibility",
content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
val transition = updateTransition(visible, label)
...
}
从源码可以看到默认入场动画是淡入 fadeIn
+ 展开 expandIn
,默认出场动画是淡出 fadeOut
+ 缩小 shrinkOut
,而且 AnimatedVisibility
的底层实现是 Transition
,
EnterTransition / ExitTransition
想要对入场出场动画做更多的定制,无疑就是通过参数 enter
和 exit
传入想要的效果了。
入场动画的类型是 EnterTransition,EnterTransition 是一个密封类且仅有一个子类 EnterTransitionImpl
private class EnterTransitionImpl(override val data: TransitionData) : EnterTransition()
EnterTransitionImpl 只有一个单参数构造函数,参数类型是 TransitionData,再点进去看看
internal data class TransitionData(
val fade: Fade? = null,
val slide: Slide? = null,
val changeSize: ChangeSize? = null,
val scale: Scale? = null
)
看起来是可见性动画的配置类,可配置的角度有 4 个:
- fade 淡化
- slide 滑动
- changSize 大小改变
- scale 缩放
密封类 EnterTransition 是无法创建实例的,而它的唯一子类 EnterTransitionImpl 又是 private class,也就是说我们无法手动创建一个 EnterTransition 实例,不仅如此,连 class TransitionData 都不是 public 的... 那怎么来配置可见性动画啊?
前面源码可以看到默认入场动画是这么写的: enter: EnterTransition = fadeIn() + expandIn()
。
fadeIn()
函数返回一个内部 TransitionData 只配置了淡入的 EnterTransitionImpl;- 而
expandIn()
返回一个内部 TransitionData 只配置了 changeSize 的 EnterTransitionImpl; - 两个 EnterTransitionImpl 相加,最终就得到了一个淡入、展开的入场可见性动画。
两个对象可以用 "+" 来相加,这是 Kotlin 运算符重载功能,这里不过多赘述。
Fade
Fade 用于配置可见性动画的淡化,也就是透明度动画。
EnterTransition | ExitTransition |
---|---|
↓ fadeIn() | ↓ fadeOut() |
fadeIn() / fadeOut()
fun fadeIn(
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
initialAlpha: Float = 0f
): EnterTransition {
return EnterTransitionImpl(TransitionData(fade = Fade(initialAlpha, animationSpec)))
}
fadeIn()
函数参数初始透明度 initialAlpha
默认值是 0,也就是说淡入的时候,默认透明度是从 0 变为 1 的。
如果我们将入场动画设置为 fadeIn(initialAlpha = 0.5f) + expandIn()
,那么在淡入的时候,透明度就会从 0.5 开始变化。
AnimatedVisibility(
visible = ...,
enter = fadeIn(initialAlpha = 0.5f) + expandIn()
){
...
}
当然,还可以利用 fadeIn()
的 animationSpec
参数来对动画过程做更详细的配置,例如动画时间、缓动曲线,只需传入一个 FiniteAnimationSpec<T>
实例。
fadeOut()
与 fadeIn()
类似,只不过是淡出,透明度从 1 变为 0,不再赘述。
Scale
Scale 用于配置可见性动画的缩放。
EnterTransition | ExitTransition |
---|---|
↓ scaleIn() | ↓ scaleOut() |
scaleIn() / scaleOut()
fun scaleIn(
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
initialScale: Float = 0f,
transformOrigin: TransformOrigin = TransformOrigin.Center,
): EnterTransition {
return EnterTransitionImpl(
TransitionData(scale = Scale(initialScale, transformOrigin, animationSpec))
)
}
animationSpec
用于配置缩放动画的规格;initialScale
用于配置缩放的初始值,因为scaleIn()
是入场动画,默认值就是 0f;transformOrigin
用于配置缩放的中心,默认是TransformOrigin.Center
。
scaleOut()
与 scaleIn()
类似,不再赘述。
Slide
Slide 用于为可见性动画配置滑入/滑出效果。
fun slideIn(
animationSpec: FiniteAnimationSpec<IntOffset> =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = IntOffset.VisibilityThreshold
),
initialOffset: (fullSize: IntSize) -> IntOffset,
): EnterTransition {
return EnterTransitionImpl(TransitionData(slide = Slide(initialOffset, animationSpec)))
}
slideIn()
函数有一个必填参数 initialOffset
,用于指定内容滑入前的偏移位置。例如要对一个正方形图案做滑入的可见性动画,可能是从左上角滑入,也可能从右上角滑入,滑入前的位置就是通过参数 initialOffset
来指定的,这是一个函数参数,接收一个 fullSize: IntSize
,这个 fullSize 就是内容的尺寸,返回的 IntOffset
就是滑入前的偏移位置。
其他几个 slideXxx()
函数都类似,不再赘述。
EnterTransition | ExitTransition |
---|---|
↓ slideIn() | ↓ slideOut() |
↓ slideInHorizontally() | ↓ slideOutHorizontally |
↓ slideInVertically() | ↓ slideOutVertically() |
ChangSize
ChangeSize 配置的是可见性动画的大小变化。
不过并没有 changSizeIn
、changSizeSmall
...这些函数,用于创建仅有大小变化的可见性动画的函数是 expandXxx
和 shrinkXxx
,expand 是“展开”的意思,而 shrink 是缩小的意思。
fun expandIn(
animationSpec: FiniteAnimationSpec<IntSize> =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = IntSize.VisibilityThreshold
),
expandFrom: Alignment = Alignment.BottomEnd,
clip: Boolean = true,
initialSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
): EnterTransition {
return EnterTransitionImpl(
TransitionData(
changeSize = ChangeSize(expandFrom, initialSize, animationSpec, clip)
)
)
}
当你尝试将入场动画设置为 expandIn()
时可能会感到疑惑,expandIn()
的参数 expandFrom
默认值不是 Alignment.BottomEnd
吗?怎么是从左上角进来的啊
Box {
AnimatedVisibility(
enter = expandIn(),
...
) {
Image(...)
}
}
是这样的,所谓的尺寸大小变化,是靠“剪”出来的。expandFrom = Alignment.BottomEnd
的意思就是说从右下角开始剪:
因为剪出来的部分每次都贴到 Box 的左上角,看起来自然就像是从左上角展开的了。当然,剪或不剪可以自由选择,expandIn()
函数有一个 clip: Boolean
参数,默认值为 true
。如果将上面的例子设置成 clip = false
,那么大小范围外的内容也一样会被绘制出来
因为例子里的图片位于 Box 的左上角,所以看不出区别,我们先把图片放在屏幕中央,再来观察 clip 参数设置为 true
和 false
时的差别。
最后一个参数 initialSize
相信不用解释了,和 slideXxx()
函数里面的参数一样。
EnterTransition | ExitTransition |
---|---|
↓ expandIn() | ↓ shrinkOut() |
↓ expandHorizontally() | ↓ shrinkHorizontally() |
↓ expandVertically() | ↓ shrinkVertically() |