0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Compose中的动画API概览及使用方法

谷歌开发者 来源:谷歌开发者 作者:谷歌开发者 2022-06-06 17:48 次阅读

我们将通过本文介绍 Compose 中的一些动画 API,并探讨如何有效地使用它们。Compose 中的动画 API 是我们构想的全新 API,这些 API 中有许多是声明式的,您可以利用声明式的方式简洁地定义动画。

这些动画 API 支持中断,当运行中的动画被另一个动画打断时,运行中动画的值会带入到新动画中。新 API 简单易用,配置了合理的默认行为,可开箱即用,也可高度定制。同时 Android Studio 还提供了强大的工具,可以帮助您制作复杂动画。

Compose 动画概览

我们先从一个简单例子开始。下图是一个猫咪图标,当我们点击按钮时,它会在隐藏和显示这两种状态间进行切换:

在 Compose 中,实现这一效果非常简单。首先我们声明一个布尔类型的 State 变量——visible,在每次点击按钮时,它的值都会被切换,而它的任何变化都会触发重组,猫咪图标也会随之出现或消失:

var visible by remember { mutableStateOf(true) } Column {        Button(onClick = { visible = !visible }) {                Text("Click")        }         if (visible) {                  CatIcon( )        }}

现在,如果我们想将此过程转变为动画,则只需将 if 语句替换为 AnimatedVisibility 可组合项即可。当 State 的值发生改变时,AnimatedVisibility 可组合项会以其状态运行动画:

AnimatedVisibility (visible) {         CatIcon( )}

还有一个 API 与 AnimatedVisibility 非常相似,那就是 AnimatedContent。AnimatedVisibility 的运行基于内容的进入和退出,而 AnimatedContent 则可为内容的变化生成过渡动画。

在下面的例子中,当我们点击按钮时,计数会随淡出和淡入效果而增加:

AnimatedContent 的 State 参数可以是任何类型,在本示例中,我们使用名为 count 的整型 State,在点击按钮时,其数值会随之增加。而每次 State 发生变化时,AnimatedContent 就会运行动画。

Row {       var count by remember { mutableStateOf (0) }        Button(onClick = { count++ }) {               Text("Add")       }       AnimatedContent (targetState = count) { targetCount ->              Text("Count: $targetCount")       }}

我们可以使用 lambda 参数,基于输入的 State 切换内容。AnimatedVisibility 和 AnimatedContent 都提供了合理的默认动画样式,但我们也可对其进行自定义。对于 AnimatedVisibility,可以自定义其进入和退出的过渡动画;对于 AnimatedContent,则可以使用 transitionSpec 参数自定义进入、退出过渡动画的组合。

AnimatedVisibility (        visible = visible,       enter = fadeIn()+ scaleIn(),       exit = fadeOut() + scaleOut()) {       // ……} AnimatedContent(       targetState = … ,       transitionSpec = {               fadeIn() + scaleIn() with fadeOut() + scaleOut()       }) { targetState ->       // ……}

下图中列出了一些进入和退出的过渡动画,其中包括 fadeIn、fadeOut、slideIn、slideOut 以及 scaleIn 和 scaleOut,这些过渡动画效果如下:

AnimatedVisibility 和 AnimatedContent 已经可以应对诸多场景,不过我们还提供了一些更为通用的 API。animate*AsState API 可用于为单个值制作动画,您只需将各种数据类型与 animate*AsState 函数组合,即可将其转换为对应的动画值。在本示例中,我们为 dp 值制作动画,所以我们使用 animateDpAsState。

val offsetX by animateDpAsState(        if (isOn) 512.dp else 0.dp)

我们开始时有提到,基于 State 的 API 支持中断。也就是说,如果播放中动画的状态发生变化,新动画将从当前的中间值和速度开始,并基于弹簧的物理效果继续播放。我们将这样的动画行为称为 AnimationSpec。

Spring 是默认的 AnimationSpec。Compose 还提供了其他类型的 AnimationSpec。例如,tween 是基于持续时间的 AnimationSpec,它根据动画由始至终的持续时间来定义运动效果。

我们可以通过下面的例子了解如何为 animate*AsState 指定 AnimationSpec。在这个例子中,我们指定动画的播放时长为三秒钟:

val offsetX by animateDpAsState(        if (isOn) 512.dp else 0.dp,        animationSpec = tween(durationMillis = 3000))

那么,如果需要同时为多个值制作动画,应该怎么做?您可以使用 updateTransition API,它对构建非常复杂的动画大有助益。我们来看一个简单的例子,下图是一个填充了颜色的方块,我们要为方块的大小和颜色这两个值同时制作动画:

首先,我们需要定义 BoxState。这是一个枚举类型,代表动画的目标,可以是 Small 或者 Large:

private enum class BoxState (        Small,        Large}

然后,我们为其创建一个 State 对象,改变 State 的值会触发动画:

var boxState by remember { mutableStateOf (BoxState.Small) }

然后我们使用 updateTransition 创建 Transition 对象。注意,最好为 Transition API 中所使用的对象附上标签,以便 Android Studio 可以更好地展示动画,这点我们稍后再介绍:

val transition = updateTransition(        targetState = boxState,        label = "Box Transition")

之后,我们就可以使用 animateColor 和 animateDp 等扩展函数创建动画值了。这些函数的返回值都是 State 对象,因此其使用方式与其他 State 相同:

val color by transition.animateColor(label = "Color") { state ->        when (state) {                BoxState.Small -> Blue                 BoxState.Large -> Orange        }}val size by transition.animateDp (label = "Size") { state ->        when (state) {                BoxState.Small -> 32.dp                  BoxState.Large -> 128.dp        }}

将目前为止我们了解的所有内容结合,便可以实现非常复杂的动画。

示例中使用了 updateTransition 为多个值制作动画,例如表格的高度、位置及其内容的透明度。同时还使用了 AnimatedVisibility 自定义进入和退出过渡动画,从而实现了理想的淡入和淡出效果。

Android Studio 动画检查工具

现在我们已经知道了如何创建复杂的动画,接下来,我们看看 Android Studio 如何帮助我们实现精美的动画效果。Android Studio 提供了动画预览功能来帮您快速验证动画效果,它会自动检测动画的使用,您可以在 Android Studio 中直接播放动画;Android Studio 还可以图形化动画的值,以便您可以快速浏览这些值是如何随时间变化的:

这里要注意的是,我们在前面生成 Transition 对象时添加的标签,会在检测到的动画列表中,作为选项卡的名称展示出来。

如下图所示,Compose 预览上的对应图标按钮表示界面中存在可检查的动画,点击按钮即可启用动画检查:

该工具目前支持 AnimatedVisibility 和 updateTransition,但我们正计划添加对 AnimatedContent 和 animate*AsState 的支持。

如下图所示,我们可以使用动画检查窗口来播放、浏览和慢放 AnimatedVisibility:

此工具还可绘制动画曲线,以便您将其与设计师所设计的运动参数进行对比,这有助于确保动画值的正确编排:

使用协程完成复杂动画

现在,我们已经了解了基于 State 的各种动画 API,它们十分有助于我们在常见用例中为 State 变化制作动画。而如果是更为复杂的场景,比如需要为动画指定自定义行为时又该怎么做呢?

例如,在某些情况下需要对动画进行更多控制,您可能需要对动画或动画集进行排序;又或者,您可能希望在动画中断时执行自定义行为。

正如我们所知,当动画中断时,基于 State 的动画 API 会保持动画值和速度的连续性。但在某些情况下,为了强调手势或响应,您可能并不需要连续性。例如,在下图中双击点赞这一动画中,再次双击时,播放中的动画会从头播放:

这种情况下,您可能需要使用目标不明确的不确定动画。我们将这种动画称之为投掷行为 (Fling),投掷行为的目标仅来自起始条件及其衰减函数。

当我们为了应对复杂的场景,而需要协调动画的编排时,就要用到 Kotlin 的一项强大功能——协程。下面的示例中是一个基础的协程动画 API——animate。使用它创建的动画,会以 initialValue 参数和可选的 initialVelocity 参数所确定的开始条件运行至 targetValue 所指定的值;可选的 animationSpec 可用于自定义运动参数,该参数的默认值为 spring();最后,我们传入函数参数 block,animate 会在每帧动画上使用最新的动画值和速度调用此参数。

suspend fun animate(        initialValue: Float,        targetValue: Float,         initialVelocity: Float = 0f,        animationSpec: AnimationSpec<Float> = spring(),         block: (value: Float, velocity: Float) -> Unit)

注意 animate 函数的 suspend 修饰符,这意味着此函数可在协程中使用,并且可以挂起协程直到动画完成。这是对动画进行排序的关键。下图展示了在协程中执行 animate 函数的过程。您会注意到,一旦调用了 animate 函数,调用动画的协程就会被挂起,直到动画结束。之后,协程将恢复并执行后续工作。

这有助于我们对操作进行排序,以及在动画后执行任务。以往,我们会将此类任务置于动画结束监听器中,而有了协程,便无需结束监听器。

下面是生成上图所示工作流的代码。我们首先使用 rememberCoroutineScope 在组合内部创建 coroutineScope,然后使用 launch 函数在该作用域内创建一个新的协程。在新的协程中,首先调用 animate。animate 只会在动画结束后返回,因此,动画结束后需要完成的任何任务,如更新状态或者启动另一个动画都可以放在 animate 后面。而如果需要取消动画,我们可以直接取消执行动画的协程。

val scope = rememberCoroutineScope()        scope.launch { // 创建新的协程                animate(...)                // 更新状态、开启另一个动画,等等                subsequentWork()}

如下图所示,如果用另一个 animate 函数替换 subsequentWork 函数,就可以得到两个连续运行的动画。如果查看代码,您会发现我们仅使用了两个连续的 animate 函数便可以实现连续动画。

val scope = rememberCoroutineScope()        scope.launch { // 创建新的协程                animate(...)                 animate(...)}

现在我们已经了解如何构建连续动画,那么如果我们想同时运行动画的话,该怎么做?

我们可以将动画分别放在单独的协程中并行运行。为此,我们需要使用 CoroutineScope。CoroutineScope 定义了在其作用域内所创建的新协程的生命周期。在该作用域内,可使用协程构建器函数 launch 来创建新的协程。launch 是非阻塞函数,所以我们可以并行创建多个协程,并在其中同时运行动画。

除了高亮的 launch 函数外,下面的示例代码与之前展示的连续动画代码相同,都可以创建新的协程。如前所述,launch 是非阻塞函数,所以,新的协程可以并行创建,并且动画将在同一帧开始运行。

val scope = rememberCoroutineScope()scope.launch {        launch { // 创建新的协程                animate(...)        }        launch { // 创建新的协程                animate(...)        }}

现在,我们完成了同时运行的动画。一言以蔽之,协程有助于极其灵活地协调动画。我们可以在同一个协程中轻松执行两个 animate 函数来创建连续的动画;我们还可以在不同的协程中运行动画,从而同时运行这些动画。这些都是更为复杂动画的组成部分。

在接下来的示例中,我们要创建双击点赞的心形动画:

如下图所示,这个动画包含两个阶段: 首先,我们需要在心形进入时,淡入并放大心形;进入动画完成后,启动退出动画以淡出,同时进一步放大心形。

为此,我们可以创建两个 CoroutineScope,一个用于进入动画,另一个用于退出动画。当作用域内的所有动画运行完成后,CoroutineScope 才会返回,因此,进入和退出动画将连续运行。在每个 CoroutineScope 中,我们使用 launch 函数创建新的协程,使淡入淡出和缩放动画可以同时运行。

在使用代码构建此动画时,首先要为 alpha 和 scale 创建 MutableState 对象,以便在动画过程中更新它们的值。然后需要创建两个 CoroutineScopes,以便连续运行进入动画和退出动画。在每个 CoroutineScope 中,我们将使用 launch 函数分别创建单独的协程,从而使淡入淡出和缩放动画可以同时运行。在动画运行期间,我们使用 animate 函数中的 lambda 更新 alpha 或 scale。

var alpha by remember { mutableStateOf(0f) }var scale by remember { mutableStateOf(0f) }        scope.launch {                 coroutineScope {                        launch { // 淡入                                animate(0f, 1f) { value, _ -> alpha = value }                        }                        launch { // 放大                                animate(0f, 2f) { value, _ -> scale = value }                        }                }                caroutineScope (                        launch { // 淡出                                animate(1f, 0f) { value, _ -> alpha = value }                        }                        launch { // 放大                                 animate(2f, 4f) { value, _ -> scale = value }                        }                }}

在了解协程动画的基础知识之后,接下来我们讲解一个更为复杂的用例。这是一个表示内容正在加载的动画,在等待内容加载时,有一个渐变条从上到下反复扫描。内容加载后,如果渐变条仍在扫描中,我们将等待该次扫描动作完成,然后再次从上到下,执行最后一次扫描并显示内容:

为了实现这一效果,我们首先需要创建一个 Animatable 对象,它将跟踪动画的值和速度。在使用 Animatable 对象创建新动画时,我们只需提供新的目标值,当前值和速度会默认转为新动画的开始条件。

@Composable fun LoadingOverlay(isLoading: State<Boolean>) {        val fraction = remember { Animatable(0f) } 

然后在 LaunchedEffect 创建的 coroutineScope 中,我们会使用 Animatable 的两个挂起函数: 一个是 animateTo,另一个是 snapTo。AnimateTo 将从 Animatable 的当前值和速度开始,向新的目标值运行动画;snapTo 会在不使用任何动画的情况下取消任何正在运行的动画,并更新 Animatable 的值。

var reveal = { mutableStateOf(false) }LaunchedEffect(Unit) {        while(isLoading.value) {                fraction.animateTo(1f, tween (2000))                 fraction, snapTo(Of)        }}

由于我们要让渐变条从上到下移动,随后返回顶部,所以需要首先以 1 为目标调用 animateTo,同时使用 2,000 毫秒的补间动画。然后通过 snapTo 让渐变条返回顶部。由于 animateTo 和 snapTo 均为挂起函数,所以我们可对其排序,并在 while 循环中重复该序列,直到加载完成。

由于我们只在每次扫描之前检查加载状态,所以任何对加载状态的更改只会在当前扫描完成后生效。这样一来,我们就创建了一个自定义的中断处理行为。它的功能不同于基于 State 的动画 API,内容加载完成后,我们便退出 while 循环,并在执行最后一次扫描前,更改显示状态、制作渐变条移动至底部的动画。

reveal = truefraction.animateTo(1f,tween(1000))

最后,当 reveal 的值变为 true 时,我们停止在此叠加层中绘制不透明的封面,以便在最后一次扫描时显示下方的内容:

if (!reveal) {        // 渐变条下的不透明覆盖        Box(Modifier.background(backgroundColor))}

这样一来,我们就完成了这个动画效果。完整的代码示例如下:

@Composable fun LoadingOverlay(isLoading: State<Boolean>) {        val fraction = remember { Animatable(0f) }         var reveal = { mutableStateOf(false) }        LaunchedEffect(Unit) {            while(isLoading.value) {                fraction.animateTo(1f, tween (2000))                fraction. snapTo(0f)            }            reveal = true            fraction.animateTo(1f, tween(1000))        }        if (!reveal) {            // 渐变条下的不透明覆盖            Box(Modifier.background(backgroundColor))        }        ……}

尾声

最后,让我们一同欣赏由社区开发者所构建的精彩动画:

上面这些动画只是开发者社区创造力的冰山一角。在我们重新构想并为 Compose 构建动画 API 的过程中,我们收到了很多来自社区的反馈。这些反馈帮助我们打造出直观又实用的 API,我们非常感谢大家所有的反馈,欢迎继续提出。

原文标题:使用 Jetpack Compose 实现精美动画

文章出处:【微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • API
    API
    +关注

    关注

    2

    文章

    1483

    浏览量

    61797
  • 动画
    +关注

    关注

    0

    文章

    20

    浏览量

    8496
  • android studio
    +关注

    关注

    0

    文章

    8

    浏览量

    1175

原文标题:使用 Jetpack Compose 实现精美动画

文章出处:【微信号:Google_Developers,微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    光纤收发器的使用方法和注意事项

    光纤收发器作为光纤通信系统的关键设备,其正确的使用方法和注意事项对于确保网络传输的稳定性和可靠性至关重要。光纤收发器作为光纤通信系统的关键设备,其正确的使用方法和注意事项对于确保网
    的头像 发表于 08-26 15:20 725次阅读

    DC/DC模拟的基本使用方法和特性确认方法

    本篇介绍了DC/DC模拟的基本使用方法及确认基本特性的方法
    的头像 发表于 08-20 17:08 564次阅读
    DC/DC模拟的基本<b class='flag-5'>使用方法</b>和特性确认<b class='flag-5'>方法</b>

    图片动画控件和Video image控件的使用方法

    在UI开发过程,序列帧基本是绕不开的,AWTK 支持多种方法实现序列帧显示,本文介绍图片动画控件和Video image控件的使用方法
    的头像 发表于 08-06 16:44 738次阅读
    图片<b class='flag-5'>动画</b>控件和Video image控件的<b class='flag-5'>使用方法</b>

    【AWTK使用经验】如何实现序列帧动画

    目前想在AWTK显示炫酷流畅的图片动画,此时可以用video_image控件来播放序列帧动画。本篇文章将介绍该控件的原理和使用方法。图1ZTP800示教器运行v
    的头像 发表于 07-18 08:25 361次阅读
    【AWTK使用经验】如何实现序列帧<b class='flag-5'>动画</b>

    浅谈锡膏的储存及使用方法

    锡膏(焊锡膏)是电子组装过程中常用的材料,它的储存和使用方法对保证焊接质量和性能至关重要。以下是详细的储存及使用方法
    的头像 发表于 06-27 10:02 758次阅读

    可编程电源使用方法

    可编程电源使用方法 可编程电源使用方法 摘要:本文详细介绍了可编程电源的使用方法,包括其基本概念、主要功能、选择原则、操作步骤、注意事项以及实际应用案例,旨在帮助读者全面了解可编程电源
    的头像 发表于 06-10 15:29 885次阅读

    手柄控制代码及使用方法

    手柄控制代码及使用方法
    的头像 发表于 05-15 10:19 1570次阅读

    讯飞星火API接入机体设备的方法与代码

    讯飞星火API接入机体设备的方法与代码
    的头像 发表于 05-15 09:56 863次阅读

    PyTorch激活函数的全面概览

    为了更清晰地学习Pytorch的激活函数,并对比它们之间的不同,这里对最新版本的Pytorch的激活函数进行了汇总,主要介绍激活函数的公式、图像以及使用方法,具体细节可查看官方文档。
    的头像 发表于 04-30 09:26 495次阅读
    PyTorch<b class='flag-5'>中</b>激活函数的全面<b class='flag-5'>概览</b>

    555集成芯片的使用方法

    555集成芯片的使用方法主要依赖于其特定的引脚功能和电路设计。
    的头像 发表于 03-25 14:39 1315次阅读

    集成芯片的使用方法

    需要注意的是,不同类型的集成芯片具有不同的使用方法和功能,因此在实际应用,需要仔细阅读芯片的数据手册和相关文档,了解其具体的使用要求和步骤。此外,对于复杂的电路设计和系统应用,可能需要具备相应的电子技术和专业知识。
    的头像 发表于 03-19 15:59 1261次阅读

    RA MCU的CRC模块和使用方法

    瑞萨RA单片机硬件CRC计算单元采用固定的多项式发生器来计算8位或者32位数据的CRC校验值,对数据传输或数据存储的一致性、完整性进行验证。这篇文章重点介绍RA MCU的CRC模块和使用方法
    发表于 02-26 11:45 835次阅读
    RA MCU<b class='flag-5'>中</b>的CRC模块和<b class='flag-5'>使用方法</b>

    PHP数组的使用方法

    PHP数组的使用方法! PHP是一种广泛使用的网络编程语言,它的数组功能非常强大且灵活。数组是一种数据结构,它允许我们在单个变量存储多个值。 在本篇文章,我将详细解释PHP数组的
    的头像 发表于 01-12 15:11 499次阅读

    PCBA焊接电路电烙铁的使用方法

    PCBA焊接电路电烙铁的使用方法的相关知识。
    的头像 发表于 12-26 10:27 749次阅读

    如何利用树莓派安装Docker和Docker-compose呢?

    本文主要演示了树莓派如何安装Docker和Docker-compose的过程。
    的头像 发表于 12-14 16:19 2759次阅读
    如何利用树莓派安装Docker和Docker-<b class='flag-5'>compose</b>呢?