Jetpack Compose中的动画
动画是创造一种不容易被发现的视觉效果的方法。动画使你的应用程序感觉更有活力和互动性。
Jetpack Compose有一套灵活而动态的接口(API),可以简单地在你的应用程序的用户界面中添加动作,从而大大改善用户体验(UX)。
在本教程中,我们将学习如何使用Jetpack Compose创建简单的动画并对其进行自定义。
前提条件
要跟上本教程,你将需要。
- 确保你的电脑上安装有最新版本的[Android Studio]。
- 熟悉Jetpack[Compose]的基本概念。如果你还不熟悉Compose,你可以通过[本节的教程]。
动画的类型
Compose中的动画被分为两个主要的组别。
1.高水平的动画
这些动画由大多数应用程序中使用的最常见的API组成。它们的设计符合Android设计指南和Material Design Motion。
高级别的动画又分为两组。
-
布局中的内容变化。当你想对外观/消失进行动画处理或改变布局中的内容时,就会应用这些动画。它们包括
- AnimationVisibility
- 动画内容
- 交叉渐变
-
基于状态的动画。这些动画的重点是用户界面的组成和重新组合。它们使用状态作为运动的决定因素。它们包括。
- 过渡动画,如
rememberInfiniteTransition animate<type>AsState.<type>,根据你的使用情况,可以取值,如颜色、浮点、Int、偏移、大小、值等。
- 过渡动画,如
我们将在本教程的后面用例子来讨论这些。
2.低级别的动画
这些是基础的API,高水平的API是建立在这些基础之上的。
让我们来看看布局变化。
动画化的可见性
@ExperimentalAnimationApi
@Composable
fun AnimVisibility() {
var isVisible by remember { mutableStateOf(true) }
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(visible = isVisible) {
Text(text = "Animating Text")
}
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
isVisible = !isVisible
}) {
Text(text = "Animate")
}
}
}
在这个例子中,我们正在对一个文本组件的可见性进行动画处理。
注意:我们使用的是
AnimatedVisibilityAPI,在编写本教程时,它还是实验性的。由于这个原因,我们必须用@ExperimentalAnimationApi注解来注释可合成的东西。
默认情况下,文本从其容器的顶部垂直动画到底部。
自定义动画
定制动画是指应用某些默认情况下不应用于动画的属性的能力。你可以使用内置的类或创建你自己的具有所需行为的自定义类。
自定义动画的可见性
AnimatedVisibility 可组成的可以通过提供AnimatedVisibility API中的属性来定制。这些属性包括。
Visible- 一个布尔值,决定内容是否可见。Enter- 一个动画,当可合成的内容第一次被显示时,它被播放。Exit- 一个动画,当可合成的内容被隐藏时播放。Modifier- 应用于动画的可合成物的修改属性。
代码示例。
@ExperimentalAnimationApi
@Composable
fun AnimVisibility() {
var isVisible by remember { mutableStateOf(true) }
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(
// customize with tween AnimationSpec
animationSpec = tween(
durationMillis = 1000,
delayMillis = 100,
easing = LinearOutSlowInEasing
)
),
// you can also add animationSpec in fadeOut if need be.
exit = fadeOut() + shrinkHorizontally(),
) {
Text(text = "Animating Text")
}
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
isVisible = !isVisible
}) {
Text(text = "Animate")
}
}
}
在这里,我们使用fadeIn 和fadeOut 动画规格来对文本的可见性进行动画处理。
Tween是一个预定义的动画规格,可以用来指定动画的delay 、duration 和easing 。缓和指的是在开始-结束的互操作中动画的加速。
除了tween之外,我们还可以使用。
- spring - 创建一个弹簧/弹跳的动画。
- keyframes - 创建一个基于位置的动画。
- 捕捉
- 可重复
- infiniteRepeatable - 创建一个无限循环的动画。
我们将在基于状态的动画部分应用和探索这些。
动画内容(AnimatedContent
这是Transition 类的一个扩展函数,通常与AnimatedVisibility 一起使用。它被用来对一个可组合的内容进行动画处理,如下图所示。
@ExperimentalAnimationApi
@ExperimentalMaterialApi // For material components such as Card.
@Composable
fun AnimContent() {
var itemExpanded by remember { mutableStateOf(false) }
val contentTransition = updateTransition(itemExpanded, label = "Expand")
Card(
modifier = Modifier.padding(6.dp),
shape = RoundedCornerShape(12.dp),
elevation = 4.dp,
onClick = { itemExpanded = !itemExpanded }
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Hi, Compose!")
// Add Animated visibility
contentTransition.AnimatedVisibility(
visible = { isVisible -> isVisible }
) {
Text(text = "What a beautiful animation!")
}
// Add Animated content
contentTransition.AnimatedContent { targetState ->
if (targetState) {
Text(text = "Expanded")
} else {
Text(text = "Click to expand")
}
}
}
}
}
💡提示。我们可以对同一个可组合的内容应用一个以上的动画。
交叉渐变
Crossfade的工作原理是接受一个目标,每当这个目标发生变化时,它就在新旧状态之间制作动画。
Crossfade(targetState = myTarget){ myTarget ->
when(myTarget){
MyTarget.First -> {
// render first state
}
MyTarget.Second -> {
// render second state
}
...
}
}
"myTarget" 参数是传递给可组合的状态。最好的方法是通过使用enum 类来定义不同的目标状态,这样就可以使用when 表达式在它们之间轻松切换。
基于状态的动画
这些也被称为animate as state 动画,因为它们会返回一个状态对象,其值会不断变化,直到动画结束。为了补充我们前面提到的内容,让我们看一下下面这个使用animateDpAsState 的例子。
在这个例子中,我们将使用它的xOffSet ,对一个盒子的位置进行动画处理。xOffset 指的是该组件沿x轴离原点有多远。
xOffset的值将由盒子的当前状态决定,因此我们需要创建一个枚举类来定义不同的可能状态。
private enum class MyBoxState { START, END }
最初,我们将把状态设置为START 。
var myBoxState by remember { mutableStateOf(MyBoxState.START) }
// swap the target value based on the current state
val xOffset by animateDpAsState(
targetValue = if (myBoxState == MyBoxState.START) 300.dp else 0.dp
)
因此,当按钮被点击时,我们会改变状态。这将启动一个受影响的可组合物的智能重组。
myBoxState =
when (myBoxState) {
MyBoxState.START -> MyBoxState.END
else -> MyBoxState.START
}
下面是完整的代码示例。
private enum class MyBoxState { START, END }
@Composable
fun AnimMyBox() {
var myBoxState by remember { mutableStateOf(MyBoxState.START) }
val xOffset by animateDpAsState(
targetValue = if (myBoxState == MyBoxState.START) 300.dp else 0.dp
)
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(fraction = 0.1F)
) {
Box(
modifier = Modifier
.height(50.dp)
.absoluteOffset(xOffset)
.background(Color.DarkGray)
) {
Text(text = "My Box")
}
}
Row(
modifier = Modifier.fillMaxSize(fraction = 1F),
horizontalArrangement = Arrangement.Center
) {
Button(onClick = {
myBoxState =
when (myBoxState) {
MyBoxState.START -> MyBoxState.END
else -> MyBoxState.START
}
}) {
Text(text = "Animate")
}
}
}
}
就像在AnimatedVisibility ,我们可以通过提供animationSpec进一步定制这个。让我们用spring 为例。
val xOffset by animateDpAsState(
targetValue = if (myBoxState == MyBoxState.START) 0.dp else 300.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
)
阻尼比是振荡阻尼与振荡周期的比率。阻尼比可以指定为:High,Medium,Low bouncy。
总结
在本教程中,我们已经介绍了Jetpack Compose中动画的基本概念,以及我们如何定制动画。Compose仍然很年轻,而且在不断发展。请继续学习,以跟上新的功能和API的改进。