美文网首页
Jetpack Compose动画

Jetpack Compose动画

作者: 海_3efc | 来源:发表于2022-03-10 14:55 被阅读0次

    前面讲到布局基础图像绘制,本篇来讲下Jetpack Compose动画。
    介绍动画主要从下图中几点进行讲解

    动画学习目录

    一、内容动画

    与布局内容变化相关的几种动画,官方称之为高级别动画API。

    • AnimatedVisibility,实验性功能,可组合项可为内容的出现和消失添加动画效果;
    • AnimatedContent,实验性功能,可组合项在内容根据目标状态发生变化时,添加内容的动画效果;
    • AnimateContentSize,可组合项内容大小发生变化动画;
    • Crossfade,可组合项的淡入淡出;

    AnimatedVisibility
    使用如下:

    var editable by remember { mutableStateOf(true) }
    AnimatedVisibility(visible = editable) {
        Text(text = "Edit")
    }
    

    AnimatedContent
    示例:

    AnimatedContent(targetState = member,
                transitionSpec ={
                    // Compare the incoming number with the previous number.
                    if (targetState > initialState) {
                        // If the target number is larger, it slides up and fades in
                        // while the initial (smaller) number slides up and fades out.
                        slideInVertically({ height -> height }) + fadeIn() with
                                slideOutVertically({ height -> -height }) + fadeOut()
                    } else {
                        // If the target number is smaller, it slides down and fades in
                        // while the initial number slides down and fades out.
                        slideInVertically({ height -> -height })+ fadeIn() with
                                slideOutVertically({ height -> height }) + fadeOut()
                    }.using(
                        // Disable clipping since the faded slide-in/out should
                        // be displayed out of bounds.
                        SizeTransform(clip = false)
                    )
                }
            ) {targetCount->
                Text(text = "$targetCount")
            }
    

    AnimatedContentSize
    示例:

    Text(text = if (!isExpanded) "点我展开" else "点我收起\n收起",modifier = Modifier
                .fillMaxWidth()
                .animateContentSize()
                .clickable { isExpanded = !isExpanded })
    

    Crossfade
    示例:

    //UI切换 带淡入淡出动画
              Crossfade(targetState = crossFadeState) {
                    Box(modifier = Modifier.background(color = if (it) Color.Green else Color.Gray)) {
                        if (it) Text(text = "Page A",)
                        else Text(text = "Page B")
                    }
                }
    

    上述示例展示动画:


    内容动画.gif

    二、值动画

    通知单个或多个值发生变化来设置动画,分为:多值动画单值动画重复动画
    多值动画
    定义:当状态发生改变时,多个值要一起发生改变。
    修饰符:updateTransition
    下面以颜色、大小、边框为例设置多值动画

    @Composable
    private fun MultiValueAnimation(){
        val targetState = remember {
            mutableStateOf(BoxState.Collapsed)
        }
        val transition = updateTransition(
            targetState = targetState,
            label = "hahah"
        )
    
        val rect by transition.animateRect { state ->
            when (state.value) {
                BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
                BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
            }
        }
        val borderWidth by transition.animateDp { state ->
            when (state.value) {
                BoxState.Collapsed -> 1.dp
                BoxState.Expanded -> 0.dp
            }
        }
        val color by transition.animateColor {state ->
            when(state.value){
                BoxState.Expanded->Color.LightGray
                BoxState.Collapsed->Color.Gray
            }
        }
        Surface(shape = RectangleShape,
            border = BorderStroke(width = borderWidth,color = Color.Blue),
            modifier = Modifier
                .size(rect.width.dp, rect.height.dp)
                .clickable {
                    if (targetState.value == BoxState.Expanded) targetState.value =
                        BoxState.Collapsed else targetState.value = BoxState.Expanded
                },
            color = color
        ) {
            Text(text = "多值动画")
        }
    }
    
    运行效果: 多值动画.gif

    单值动画
    定义:为单个值添加动画效果。Compose 为 Float、Color、Dp、Size、Offset、Rect、Int、IntOffset 和 IntSize 提供开箱即用的 animate*AsState 函数。通过为接受通用类型的 animateValueAsState 提供 TwoWayConverter,您可以轻松添加对其他数据类型的支持
    修饰符:animateXXAsState
    animateFloatAsStateanimateOffsetAsState为示例:

    @Composable
    private fun SingleValueAnimation(){
        var enabled by remember {
            mutableStateOf(false)
        }
        //使用animateFloatAsState变化透明度
        val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f)
        val offset by animateOffsetAsState(targetValue = if (enabled) Offset.Zero else Offset(10f,10f))
        Image(
            painter = painterResource(id = R.mipmap.ic_girl),
            contentDescription = "avatar",
            modifier = Modifier
                .offset(offset.x.dp, offset.y.dp)
                .alpha(alpha)
                .clickable { enabled = !enabled }
        )
    }
    
    运行结果: 单值动画.gif

    针对单值动画,除了上述使用方式外,也可采用Animatable来实现。
    如下:

    var enabled by remember {
            mutableStateOf(false)
        }
        val color = remember { Animatable(Color.Gray) }
        LaunchedEffect(enabled) {
            color.animateTo(if (enabled) Color.Green else Color.Red)
        }
        Box(
            Modifier
                .size(60.dp)
                .background(color.value)
                .clickable { enabled = !enabled }
        )
    
    运行效果: Animatable.gif

    重复动画
    定义:InfiniteTransition 可以像 Transition 一样保存一个或多个子动画,但是,这些动画一进入组合阶段就开始运行,除非被移除,否则不会停止。使用 rememberInfiniteTransition 创建 InfiniteTransition 实例,并使用 animateColor、animatedFloat 或 animatedValue 添加子动画。

    @Composable
    private fun InfiniteTransitionAnimation(){
        val infiniteTransition = rememberInfiniteTransition()
        val state by infiniteTransition.animateColor(
            initialValue = Color.Red,
            targetValue = Color.Cyan,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 2000,easing = FastOutSlowInEasing),
                repeatMode = RepeatMode.Reverse
            ))
        Box(
            Modifier
                .size(60.dp)
                .background(state)
        )
    }
    
    运行结果: 重复动画.gif

    三、自定义动画

    有时候系统提供的默认动画无法满足我们的需求,这样我们就需要进行自定义了。那么怎么自定义呢,先看下代码:

    val alpha: Float by animateFloatAsState(
        targetValue = if (enabled) 1f else 0.5f,
        // Configure the animation duration and easing.
        animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
    )
    

    看到有个参数为:animationSpec,其类型为AnimationSpec,该参数为可选有默认值,是定义动画的类型。因此想自定义动画,必须给该参数传值了。
    先看下有哪些类型的动画:

    image.png
    spring(弹性动画)
    定义:可在起始值和结束值之间创建基于物理特性的动画。它接受 2 个参数:dampingRatio 和 stiffness
    • dampingRatio,定义弹簧的弹性。默认值为 Spring.DampingRatioNoBouncy。
    • stiffness,定义弹簧应向结束值移动的速度。默认值为 Spring.StiffnessMedium。
      示例:
    /**
     * 弹性动画
     */
    @Composable
    private fun springAnimation(){
        var enable by remember{ mutableStateOf(true)}
        val value: Int by animateIntAsState(
            targetValue = if (enable) 200 else 50,
            // Configure the animation duration and easing.
            animationSpec = spring(
                //定义弹簧的弹性
                dampingRatio = Spring.DampingRatioHighBouncy,
                //定义弹簧应向结束值移动的速度
                stiffness = Spring.StiffnessHigh
            )
        )
        Box(
            Modifier
                .offset(50.dp)
                .width(value.dp)
                .height(50.dp)
                .background(Color.Blue)
                .clickable { enable = !enable }
        ){
            Text(text = "spring")
        }
    }
    
    运行结果 spring.gif

    tween
    在指定的 durationMillis 内使用缓和曲线在起始值和结束值之间添加动画效果。还可以指定 delayMillis 来推迟动画播放的开始时间。
    示例:

    @Composable
    private fun tweenAnimation(){
        var enable by remember{ mutableStateOf(false)}
        val value by animateIntAsState(
            targetValue = if (enable) 150 else 50,
            animationSpec = tween(
                durationMillis = 1000,
                delayMillis = 500,
                easing = LinearOutSlowInEasing
            )
        )
        Box(
            Modifier.width(value.dp)
                .height(50.dp)
                .background(Color.Gray)
                .clickable { enable = !enable }
        ) {
            Text(text = "tween")
        }
    }
    

    运行结果:

    tween.gif
    keyframes
    定义:会根据在动画时长内的不同时间戳中指定的快照值添加动画效果。在任何给定时间,动画值都将插值到两个关键帧值之间。对于其中每个关键帧,您都可以指定 Easing 来确定插值曲线。
    需要设置的配置为:
    • durationMillis 动画执行时长,单位毫秒;
    • delayMillis 延迟时间,单位毫秒;
    • keyframes 关键帧,internal类型不能直接配置,需通过KeyframesSpecConfig的扩展函数atwith结合来设置关键帧信息
      示例如下:
    @Composable
    private fun keyframesAnimation(){
        var enable by remember{ mutableStateOf(false)}
    
        val value by animateIntAsState(
            targetValue = if (enable) 200 else 50,
            animationSpec = keyframes {
                durationMillis = 2000   //动画执行时长
                delayMillis = 500       //动画延迟多久后执行
                50 at 0 with LinearOutSlowInEasing   //0 - 200ms执行的帧
                100 at 200 with FastOutLinearInEasing // 200 - 1200ms执行的帧
                150 at 1200 with LinearEasing
            }
        )
        Box(
            Modifier.height(50.dp)
                .width(value.dp)
                .background(Color.Yellow)
                .clickable { enable = !enable }
        ) {
           Text(text = "keyframes")
        }
    }
    
    运行结果: keyframes.gif

    repeatable(按次数重复动画)
    定义:反复运行基于时长的动画,直至达到指定的迭代计数。可以传递 repeatMode 参数来指定动画是从头开始 (RepeatMode.Restart) 还是从结尾开始 (RepeatMode.Reverse) 重复播放。
    可设置参数为:

    • iterations 重复次数
    • animation 有时长的动画
    • repeatMode 重复模式,有:RepeatMode.RestartRepeatMode.Reverse
      示例如下:
    /**
     * 重复动画
     */
    @Composable
    private fun repeatableAnimation(){
        var enable by remember{ mutableStateOf(false)}
    
        val value by animateIntAsState(
            targetValue = if (enable) 200 else 50,
            animationSpec = repeatable(
                iterations = 2,   //重复执行次数
                animation = tween(durationMillis = 1000),
                repeatMode = RepeatMode.Reverse  //重复执行模式,从最后开始
            )
        )
        Box(
            Modifier.height(50.dp)
                .width(value.dp)
                .background(Color.Red)
                .clickable { enable = !enable }
        ) {
            Text(text = "repeatable")
        }
    }
    

    运行效果:

    repeatable.gif
    infiniteRepeatable(无限重复动画)
    该动画类似repeatable,都是重复的迭代。但infiniteRepeatable为无限次的重复执行。
    示例如下:
    /**
     * 无限次重复动画
     */
    @Composable
    private fun InfiniteRepeatableAnimation(){
        var enable by remember{ mutableStateOf(false)}
    
        val value by animateIntAsState(
            targetValue = if (enable) 200 else 50,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 1000),
                repeatMode = RepeatMode.Reverse  //重复执行模式,从最后开始
            )
        )
        Box(
            Modifier.height(50.dp)
                .width(value.dp)
                .background(Color.Green)
                .clickable { enable = !enable }
        ) {
            Text(text = "infiniteRepeatable")
        }
    }
    

    运行结果就不展示了。
    snap
    定义:是一种特殊的 AnimationSpec类型,它会立即将值切换到结束值。您可以指定 delayMillis 来延迟动画播放的开始时间。
    以延迟1000ms为例,如下:

    /**
     * 无限次重复动画
     */
    @Composable
    private fun SnapAnimation(){
        var enable by remember{ mutableStateOf(false)}
        val value by animateIntAsState(
            targetValue = if (enable) 200 else 50,
            animationSpec = snap(
                delayMillis = 1000   //延迟1000ms执行
            )
        )
        Box(
            Modifier
                .height(50.dp)
                .width(value.dp)
                .background(Color.Green)
                .clickable { enable = !enable }
        ) {
            Text(text = "snap")
        }
    }
    
    运行结果: snap.gif

    四、手势动画

    我们使用 Animatable 表示图片组件的偏移位置为例,触摸以修饰符pointerInput。当检测到新的点按事件时,我们将调用 animateTo 以将偏移值通过动画过渡到点按位置。

    /**
     * 通过手势点击,设置偏移量动画
     */
    @Composable
    private fun Gesture(){
        val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .pointerInput(Unit) {
                    coroutineScope {
                        while (true) {
                            // Detect a tap event and obtain its position.
                            val position = awaitPointerEventScope {
                                awaitFirstDown().position
                            }
                            launch {
                                // Animate to the tap position.
                                offset.animateTo(position)
                            }
                        }
                    }
                }
        ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_girl),
                contentDescription = "avatar",
                modifier = Modifier.offset { offset.value.toIntOffset() }
            )
        }
    }
    
    运行结果: 手势动画.gif

    总结

    动画知识点结构图

    欢迎留言,一起学习,共同进步!

    github - 示例源码
    gitee - 示例源码

    相关文章

      网友评论

          本文标题:Jetpack Compose动画

          本文链接:https://www.haomeiwen.com/subject/dnhfrrtx.html