Jetpack Compose自定义动画与Animatable详解

作者:唯鹿 时间:2021-07-04 20:26:08 

本篇主要是自定义动画与Animatable

AnimationSpec

上一篇中,出现了多次animationSpec属性,它是用来自定义动画规范的。例如:

fun Modifier.animateContentSize(
   animationSpec: FiniteAnimationSpec<IntSize> = spring(),
   finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier

下面是几种自带的动画规范:

1.spring

我们上一篇也简单说到了,它是一种弹性动画。源码如下:

@Stable
fun <T> spring(
   dampingRatio: Float = Spring.DampingRatioNoBouncy,
   stiffness: Float = Spring.StiffnessMedium,
   visibilityThreshold: T? = null
): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)

dampingRatio: 定义弹簧的弹性,数值越小弹性越高。默认值DampingRatioNoBouncy为1f。下图为其他数值的效果:

Jetpack Compose自定义动画与Animatable详解

  • stiffness: 定义弹簧向结束值移动的速度。默认值StiffnessMedium为1500f。

  • visibilityThreshold:可见临界值(动画停止的值)。比如目标150dp,此值设置10dp,那么回弹距离小于这个值时就会停止弹性效果。

2.tween

在指定的时间内使用缓和曲线在起始值和结束值之间添加动画效果。

@Stable
fun <T> tween(
   durationMillis: Int = DefaultDurationMillis,
   delayMillis: Int = 0,
   easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
  • durationMillis:动画的持续时间,默认值300毫秒。

  • delayMillis:延迟动画开始的时间。

  • easing:两个值之间的插值曲线,类似Android中的Interpolator插值器。目前compose中提供了五种类型,这里就不一一说明了,实际使用时可以尝试一下。当然你也可以使用CubicBezierEasing甚至Easing自定义曲线。

3.keyframes

在动画时长内的不同时间戳中指定快照值,实现动画效果。在任何给定时间,动画值都将插值到两个关键帧值之间。对于其中每个关键帧,您都可以指定 Easing 来确定插值曲线。

@Stable
fun <T> keyframes(
   init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit
): KeyframesSpec<T> {
   return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig<T>().apply(init))
}

例子:

val value by animateFloatAsState(
   targetValue = 1f,
   animationSpec = keyframes {
       durationMillis = 375
       0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
       0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
       0.4f at 75 // ms
       0.4f at 225 // ms
   }
)

解释一下上面的例子:整体起始值0和结束值1,动画时长375毫秒。

  • 从0开始到0.2结束,前15毫秒使用LinearOutSlowInEasing过渡。

  • 从0.2开始到0.4结束,15到75毫秒使用FastOutLinearInEasing过渡。

  • 从0.4开始到0.4结束,75到225毫秒匀速过渡。(静止效果)

  • 从0.4开始到1结束,225毫秒到结束匀速过渡。

效果图如下:

Jetpack Compose自定义动画与Animatable详解

keyframes同样也可以设置delayMillis指定延时时间。

4.repeatable

重复运行基于时长的动画,直至达到指定的迭代计数。

@Stable
fun <T> repeatable(
   iterations: Int,
   animation: DurationBasedAnimationSpec<T>,
   repeatMode: RepeatMode = RepeatMode.Restart,
   initialStartOffset: StartOffset = StartOffset(0)
): RepeatableSpec<T> = RepeatableSpec(iterations, animation, repeatMode, initialStartOffset)
  • iterations,动画运行的次数。1表示不重复。建议使用infiniteRepeatable创建无限重复的动画。

  • animation,设置需要重复的动画。

  • repeatMode,重复模式。指定动画是从头开始 (RepeatMode.Restart) 还是从结尾开始 (RepeatMode.Reverse) 重复播放。

  • initialStartOffset,可用于延迟动画的开始或快进动画到给定的播放时间。如果设置StartOffset(1500)就是延迟动画。 设置StartOffset(1500, offsetType = StartOffsetType.FastForward)就是快进动画。默认情况下,延时时长为0。

如果需要动画重复无限次,可以使用infiniteRepeatable,用法与repeatable一致。

5.snap

snap是特殊的 AnimationSpec,它会立即将值切换到结束值。您可以指定 delayMillis 来延迟动画播放的开始时间。

@Stable
fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)

Animatable

Animatable 是一个值容器,它可以在通过 animateTo 更改值时为值添加动画效果。它可确保一致的连续性和互斥性,这意味着值变化始终是连续的,并且会取消任何正在播放的动画。

Animatable 的许多功能(包括 animateTo)以挂起函数的形式提供。这意味着,它们需要封装在适当的协程作用域内。例如,您可以使用 LaunchedEffect 可组合项针对指定键值的时长创建一个作用域。

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
   color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(Modifier.fillMaxSize().background(color.value))

在上面的示例中,我们创建并记住了初始值为 Color.GrayAnimatable 实例。根据布尔标记 ok 的值,颜色将以动画形式呈现 Color.GreenColor.Red。对该布尔值的任何后续更改都会使动画开始使用另一种颜色。如果更改该值时有正在播放的动画,系统会取消该动画,并且新动画将以当前速度从当前快照值开始播放。

animate*AsState 相比,使用 Animatable 可以直接对以下几个方面进行更精细的控制。首先,Animatable 的初始值可以与第一个目标值不同。例如,上面的代码示例首先显示一个灰色框,然后立即开始通过动画呈现为绿色或红色。其次,Animatable 对内容值提供更多操作(即 snapToanimateDecay)。snapTo 可立即将当前值设为目标值。如果动画本身不是唯一的可信来源,且必须与其他状态(如触摸事件)同步,该函数就非常有用。animateDecay 用于启动播放从给定速度变慢的动画(衰减)。这有助于实现投掷行为。

下面看一个官方demo:

Column(modifier = Modifier.fillMaxWidth()) {
       // Creates an `Animatable` to animate Offset and `remember` it.
       val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
       Box(
           Modifier.fillMaxSize().background(Color(0xffb99aff)).pointerInput(Unit) {
               coroutineScope {
                   while (true) {
                       val offset = awaitPointerEventScope {
                           awaitFirstDown().position
                       }
                       // Launch a new coroutine for animation so the touch detection thread is not
                       // blocked.
                       launch {
                           // Animates to the pressed position, with the given animation spec.
                           animatedOffset.animateTo(
                               offset,
                               animationSpec = spring(stiffness = Spring.StiffnessLow)
                           )
                       }
                   }
               }
           }
       ) {
           Text("Tap anywhere", Modifier.align(Alignment.Center))
           Box(
               Modifier
                   .offset {
                       // Use the animated offset as the offset of the Box.
                       IntOffset(
                           animatedOffset.value.x.roundToInt(),
                           animatedOffset.value.y.roundToInt()
                       )
                   }
                   .size(40.dp)
                   .background(Color(0xff3c1361), CircleShape)
           )
       }
   }

效果图这里我就不放了。就是页面上有个40*40dp的圆形view,点击屏幕任意位置,他就会移动过去。这里核心靠的就是animateTo方法,而且无论我们中间如果快速点击不同位置,系统会取消上一个动画,接着当前位置移动到目标位置。

Jetpack Compose自定义动画与Animatable详解

从上面这张图可以看出各个API之间的关系。所有这些 API 都基于更基础的 Animation API。虽然大多数应用不会直接使用 Animation ,但 Animation 的某些自定义功能可以通过更高级别的 API 获得。

到此,Compose的动画部分大体结束。

参考

Compose官方文档

Compose官方文档 &ndash; Animatable

来源:https://weilu.blog.csdn.net/article/details/125997035

标签:Jetpack,Compose,自定义动画,Animatable
0
投稿

猜你喜欢

  • C#使用Monitor类实现线程同步

    2021-07-20 03:01:14
  • Java中使用StackWalker和Stream API进行堆栈遍历

    2023-04-12 11:29:07
  • Java Web开发过程中登陆模块的验证码的实现方式总结

    2022-01-29 19:33:16
  • MyBatis的嵌套查询解析

    2023-11-26 16:58:46
  • Java 数据流之Broadcast State

    2022-05-21 15:17:19
  • slf4j jcl jul log4j1 log4j2 logback各组件系统日志切换

    2023-08-08 13:00:41
  • 一文读懂Spring Bean的生命周期

    2022-11-01 04:01:20
  • Android中Notification通知用法详解

    2023-02-24 02:37:51
  • Java单例模式简单示例

    2022-04-24 03:46:20
  • Java异常处理中同时有finally和return语句的执行问题

    2022-08-14 15:28:30
  • Android开发实现webview中img标签加载本地图片的方法

    2023-03-15 01:33:40
  • Linux下JDK安装教程

    2022-04-08 18:04:46
  • c#栈变化规则图解示例(栈的生长与消亡)

    2021-10-12 05:11:11
  • Spring Boot 文件上传与下载的示例代码

    2021-08-30 02:04:53
  • Java中ArrayList初始化的四种方法详解

    2022-03-29 21:50:13
  • Android Volley框架使用方法详解

    2022-04-07 09:06:49
  • JAVA实现经典扫雷游戏的示例代码

    2022-01-26 04:20:18
  • Android之有效防止按钮多次重复点击的方法(必看篇)

    2022-03-15 01:30:34
  • java实现简单猜数字

    2022-12-31 16:20:02
  • Android整理好的图片压缩工具类

    2023-11-06 03:24:19
  • asp之家 软件编程 m.aspxhome.com