compose--动画

compose中本身封装了很多动画,我们可以拿来直接使用,动画也可以从官网进行学习:Compose动画

一、AnimationSpec

compose中的动画效果都是由AnimationSpec定义的,它包含了动画执行时长,估值器,插值器的功能,我们也可以通过AnimationSpec自定义动画效果,所以在真正使用compose动画之前,先对AnimationSpec来做学习

1.spring

spring就是一个弹弹乐效果的插值器,stiffness 定义弹簧应向结束值移动的速度,dampingRatio 定义弹簧的弹性,官方给出的效果图示如下:

例子:

@Preview
@Composable
fun MySpring() {
    var targetValue by remember { mutableStateOf(0.dp) }
    val paddingTop by animateDpAsState(
        targetValue = targetValue,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioHighBouncy,
            stiffness = Spring.StiffnessMedium
        )
    )

    Box(
        modifier = Modifier
            .padding(top = paddingTop)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
            .clickable {
                targetValue = 100.dp
            }
    )
}

效果:

2.tween

tween用于指定动画执行时间 durationMillis、延迟执行时间 delayMillis 、运动速率变化 easing

2.1 基本使用
@Preview
@Composable
fun MyTween() {
    var targetValue by remember { mutableStateOf(0.dp) }
    val paddingTop by animateDpAsState(
        targetValue = targetValue,
        animationSpec = tween(2000)
    )

    Box(
        modifier = Modifier
            .padding(top = paddingTop)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
            .clickable {
                targetValue = 100.dp
            }
    )
}

效果:

2.2 easing

运动速率变化默认提供了以下几种,不指定时使用FastOutSlowInEasing

/**
 * 快速加速,逐渐减速
 */
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)

/**
 * Incoming elements are animated using deceleration easing, which starts a transition
 * at peak velocity (the fastest point of an element’s movement) and ends at rest.
 *
 * This is equivalent to the Android `LinearOutSlowInInterpolator`
 */
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)

/**
 * Elements exiting a screen use acceleration easing, where they start at rest and
 * end at peak velocity.
 *
 * This is equivalent to the Android `FastOutLinearInInterpolator`
 */
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)

/**
 * It returns fraction unmodified. This is useful as a default value for
 * cases where a [Easing] is required but no actual easing is desired.
 */
val LinearEasing: Easing = Easing { fraction -> fraction }
Easing
FastOutSlowInEasing
LinearOutSlowInEasing
FastOutLinearInEasing
LinearEasing

3.keyframes

keyframes可以选择在不同的时段,手动控制值的变化,并可以使用with指定Easing

@Preview
@Composable
fun MyKeyframes() {
    var targetValue by remember { mutableStateOf(0.dp) }
    val paddingTop by animateDpAsState(
        targetValue = targetValue,
        animationSpec = keyframes {
            // 执行时长为1s
            durationMillis = 1000

            // 在200ms内,以LinearEasing达到目标值的1/4
            targetValue / 4 at 200 with LinearEasing
            // 在200ms - 500ms,以LinearOutSlowInEasing达到目标值的1 / 2
            targetValue / 2 at 500 with LinearOutSlowInEasing
            // 在500ms - 800ms,达到目标值的3 / 4
            targetValue * 3 / 4 at 800
            // 在900ms后到执行结束,达到目标值
            targetValue at 900
        }
    )

    Box(
        modifier = Modifier
            .padding(top = paddingTop)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
            .clickable {
                targetValue = 300.dp
            }
    )
}

效果:

4.repeatable

repeatable可以为基于时长的动画(如tweenkeyframes)加上可以重复执行的效果,repeatMode 用来指定重复的模式:从头开始 (RepeatMode.Restart) , 从结尾开始 (RepeatMode.Reverse)

@Preview
@Composable
fun MyRepeatable() {
    var targetValue by remember { mutableStateOf(0.dp) }
    val paddingTop by animateDpAsState(
        targetValue = targetValue,
        animationSpec = repeatable(
            iterations = 3,
            animation = tween(2000, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    Box(
        modifier = Modifier
            .padding(top = paddingTop)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
            .clickable {
                targetValue = 100.dp
            }
    )
}

效果:

5.infiniteRepeatable

infiniteRepeatable为无限循环执行的动画

6.snap

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

@Preview
@Composable
fun MySnap() {
    var targetValue by remember { mutableStateOf(0.dp) }
    val paddingTop by animateDpAsState(
        targetValue = targetValue,
        animationSpec = snap(
            delayMillis = 50
        )
    )

    Box(
        modifier = Modifier
            .padding(top = paddingTop)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
            .clickable {
                targetValue = 100.dp
            }
    )
}

效果:

二、高级动画

高级动画就是compose专门迎合MD风格封装的动画,也足够我们在日常开发中使用了

1.AnimatedVisibility

前面我们已经使用过该组件了,AnimatedVisibility可为内容的出现和消失添加动画效果,默认为所有内容组件添加以淡入和扩大的方式出现,以淡出和缩小的方式消失

1.1 基本使用

直接上代码:

@Preview
@Composable
fun MyAnimeVisible() {
    var visible by remember { mutableStateOf(false) }

    Row {
        AnimatedVisibility(visible = visible) {
            Icon(Icons.Rounded.Build, contentDescription = null)
        }

        Button(onClick = { visible = !visible }) {
            Text("click")
        }
    }
}

效果:

1.2 EnterTransition&ExitTransition

也可以通过给AnimatedVisibility指定 EnterTransitionExitTransition 来自定义这种过渡效果, EnterTransitionExitTransition 都支持了运算符重载,可以方便的组合各个过渡效果:

@Preview
@Composable
fun MyAnimeVisible2() {
    var visible by remember { mutableStateOf(false) }
    val density = LocalDensity.current

    Row {
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically {
                // Slide in from 40 dp from the top.
                with(density) { -40.dp.roundToPx() }
            } + expandVertically(
                // Expand from the top.
                expandFrom = Alignment.Top
            ) + fadeIn(
                // Fade in with the initial alpha of 0.3f.
                initialAlpha = 0.3f
            ),
            exit = slideOutVertically() + shrinkVertically() + fadeOut()
        ) {
            Icon(Icons.Rounded.Build, contentDescription = null)
        }

        Button(onClick = { visible = !visible }) {
            Text("click")
        }
    }
}

效果:

官网给出的各个效果图示如下:

EnterTransition ExitTransition
fadeIn
淡入动画
fadeOut
淡出动画
slideIn
滑入动画
slideOut
滑出动画
slideInHorizontally
水平滑入动画
slideOutHorizontally
水平滑出动画
slideInVertically
垂直滑入动画
slideOutVertically
垂直滑出动画
scaleIn
缩放进入动画
scaleOut
缩放退出动画
expandIn
展开进入动画
shrinkOut
缩小退出动画
expandHorizontally
水平展开动画
shrinkHorizontally
水平缩小动画
expandVertically
垂直展开动画
shrinkVertically
垂直缩小动画

我们还可以通过它们的animationSpec属性,改变动画的执行过程,如执行时间、运动轨迹等

1.3 animateEnterExit修饰

此外,除了指定全体内容组件外,还记得在Modifier中可以使用animateEnterExit修饰来指定特定的内容组件的出现和消失动画吗?这种方式会和AnimatedVisibility中的动画进行组合,如果你不想要AnimatedVisibility中的默认动画效果,可以指定为 EnterTransition.NoneExitTransition.None

@OptIn(ExperimentalAnimationApi::class)
@Preview
@Composable
fun MyAnimeVisible3() {
    var visible by remember { mutableStateOf(false) }

    Row {
        AnimatedVisibility(
            visible = visible,
            enter = EnterTransition.None,//去除默认动画效果
            exit = ExitTransition.None
        ) {
            Column {
                Icon(Icons.Rounded.Build, contentDescription = null)
                Icon(
                    Icons.Rounded.Favorite, contentDescription = null,
                    // 单独使用特定的动画
                    modifier = Modifier.animateEnterExit(
                        enter = scaleIn(),
                        exit = scaleOut()
                    )
                )
            }
        }

        Button(onClick = { visible = !visible }) {
            Text("click")
        }
    }
}

效果:

1.4 transition

AnimatedVisibilityScope中可以通过transition创建自定义的动画效果:

例子,给Box设置背景颜色变化的动画:

@OptIn(ExperimentalAnimationApi::class)
@Preview
@Composable
fun MyAnimeVisible5() {
    var visible by remember { mutableStateOf(false) }

    Row {
        AnimatedVisibility(
            visible = visible,
            enter = EnterTransition.None,//去除默认动画效果
            exit = ExitTransition.None
        ) {
            val bg by transition.animateColor { state ->
                if (state == EnterExitState.Visible) Color.Blue else Color.Gray
            }

            Box(
                modifier = Modifier
                    .size(50.dp)
                    .background(bg)
            )
        }

        Button(onClick = { visible = !visible }) {
            Text("click")
        }
    }
}

效果:

2.animate*AsState

通过animate*AsState,可以创建简单的动画:

例子:

@Preview
@Composable
fun MyAnimateState() {
    var enabled by remember { mutableStateOf(true) }
    val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f)

    Row {
        Box(
            Modifier
                .size(50.dp)
                .graphicsLayer(alpha = alpha)
                .background(Color.Red)
        )

        Button(onClick = { enabled = !enabled }) {
            Text("click")
        }
    }
}

效果:

3.AnimatedContent

AnimatedContent需要绑定State状态,当状态发生改变,导致重组时,会为内容添加动画

3.1 基本使用
@OptIn(ExperimentalAnimationApi::class)
@Preview
@Composable
fun MyAnimatedContentPreview() {
    Row {
        var count by remember { mutableStateOf(0) }
        Button(onClick = { count++ }) {
            Text("Add")
        }
        
        AnimatedContent(targetState = count) { targetCount ->
            // Make sure to use `targetCount`, not `count`.
            Text(text = "Count: $targetCount")
        }
    }
}

效果:

3.2 transitionSpec

transitionSpec,可以指定内容显示和消失的动画,使用with将显示和消失动画进行结合

@OptIn(ExperimentalAnimationApi::class)
@Preview
@Composable
fun MyAnimatedContentPreview2() {
    Row {
        var count by remember { mutableStateOf(0) }
        Button(onClick = { count++ }) {
            Text("Add")
        }

        AnimatedContent(
            targetState = count,
            transitionSpec = {
                if (targetState >  5) {//如果大于5
                    // 从上到下滑入滑出,淡入淡出
                    slideInVertically { height -> height } + fadeIn() with
                            slideOutVertically { height -> -height } + fadeOut()
                } else {
                    // 从下到上滑入滑出,淡入淡出
                    slideInVertically { height -> -height } + fadeIn() with
                            slideOutVertically { height -> height } + fadeOut()
                }.using(
                    // 禁用裁剪、因为滑入滑出应该显示超出界限
                    SizeTransform(clip = false)
                )
            }
        ) { targetCount ->
            Text(text = "Count: $targetCount")
        }
    }
}

效果:

3.3 SizeTransform

SizeTransform可以更自由的在AnimatedContent执行时穿插动画效果:

@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MySizeTransformPreview() {
    var expanded by remember { mutableStateOf(false) }
    
    Surface(
        color = MaterialTheme.colorScheme.primary,
        onClick = { expanded = !expanded }
    ) {
        AnimatedContent(
            targetState = expanded,
            transitionSpec = {
                fadeIn(animationSpec = tween(150, 150)) with
                        fadeOut(animationSpec = tween(150)) using
                        SizeTransform { initialSize, targetSize ->
                            if (targetState) {// 如果展开
                                keyframes {
                                    // 在150ms内,先将宽度渐渐变为targetSize.width
                                    IntSize(targetSize.width, initialSize.height) at 150
                                    // 执行总时长1s
                                    durationMillis = 1000
                                }
                            } else {
                                keyframes {
                                    // 在150ms内,先将高度渐渐变为targetSize.width
                                    IntSize(initialSize.width, targetSize.height) at 150
                                    // 执行总时长1s
                                    durationMillis = 1000
                                }
                            }
                        }
            }
        ) { targetExpanded ->
            if (targetExpanded) {
                Expanded()
            } else {
                ContentIcon()
            }
        }
    }
}

@Composable
fun Expanded() {
    Box(contentAlignment = Alignment.Center) {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.matchParentSize(),
            contentScale = ContentScale.FillBounds
        )
        Text(
            "hi,This creates a SizeTransform with the provided clip \n" +
                    "and sizeAnimationSpec. By default, clip will be true. \n" +
                    "This means during the size animation, the content will be clipped to the animated size.\n" +
                    " sizeAnimationSpec defaults to return a spring animation."
        )
    }
}

@Composable
fun ContentIcon() {
    Box(contentAlignment = Alignment.Center) {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.matchParentSize(),
            contentScale = ContentScale.FillBounds
        )
        Icon(Icons.Rounded.Call, contentDescription = null)
    }
}

效果:

4.animateContentSize修饰

animateContentSize修饰会在内容大小发生变化时,有一个动画效果,直接变化会导致显得突兀:

@Preview
@Composable
fun MyAnimateContentSizePreview() {
    val textArr = arrayOf("hello", "hi", "hello world")
    var textPosition by remember { mutableStateOf(0) }

    Column {
        Box(
            modifier = Modifier
                .background(MaterialTheme.colorScheme.primary)
                .animateContentSize()
                .padding(10.dp)
        ) {
            Text(textArr[textPosition], color = MaterialTheme.colorScheme.onPrimary)
        }

        Button(onClick = { textPosition = Random.nextInt(textArr.size) }) {
            Text(text = "点我", color = MaterialTheme.colorScheme.onPrimary)
        }
    }
}

效果:

5.Crossfade

Crossfade会在内容组件重组时,有一个淡入淡出的动画效果:

@Preview
@Composable
fun MyCrossfade() {
    var currentPage by remember { mutableStateOf("A") }

    Column() {
        Crossfade(targetState = currentPage) { screen ->
            when (screen) {
                "A" -> Text(
                    text = "Page A",
                    modifier = Modifier
                        .background(MaterialTheme.colorScheme.surface)
                        .padding(10.dp),
                    color = MaterialTheme.colorScheme.onSurface
                )
                "B" -> Text(
                    text = "Page B",
                    modifier = Modifier
                        .background(MaterialTheme.colorScheme.error)
                        .padding(10.dp),
                    color = MaterialTheme.colorScheme.onError
                )
            }
        }

        Button(onClick = {
            if (currentPage == "A") {
                currentPage = "B"
            } else {
                currentPage = "A"
            }
        }) {
            Text("click")
        }
    }
}

效果:

6.Transition

AnimatedVisibilityScope中可以直接获取到transition进而自定义一些动画,该对象为Transition,可管理一个或多个动画作为其子项,并在多个状态之间同时运行这些动画,通过Transition也可以直接使用AnimatedVisibilityAnimatedContent

6.1 基本使用updateTransition

通过updateTransition获取一个Transition对象,结合状态定义动画效果:

@Preview
@Composable
fun MyTransition() {
    var currentState by remember { mutableStateOf(MyState.Normal) }
    // 定义Transition对象
    val transition = updateTransition(currentState)

    // 根据状态改变边框线宽度
    val borderWidth by transition.animateDp { state ->
        when (state) {
            MyState.Normal -> Dp.Hairline
            MyState.Expand -> 1.dp
        }
    }

    Column(
        Modifier
            .background(MaterialTheme.colorScheme.surface)
            .border(borderWidth, MaterialTheme.colorScheme.outline)
            .padding(10.dp)
    ) {
        Button(onClick = {
            currentState = if (currentState == MyState.Normal) {
                MyState.Expand
            } else {
                MyState.Normal
            }
        }) {
            Text("click")
        }
    }
}

效果:

6.2 transitionSpec

AnimatedContenttransitionSpec类似,TransitiontransitionSpec可以为过渡状态变化的指定不同的AnimationSpecAnimationSpec可以用于改变动画的执行过程,在传统安卓开发中,我们称之为插值器:

@Preview
@Composable
fun MyTransition2() {
    var currentState by remember { mutableStateOf(MyState.Normal) }
    // 定义Transition对象
    val transition = updateTransition(currentState)
    val contentWidth = 100.dp
    val contentHeight = 50.dp

    // 根据状态改变边框线左边坐标
    val offsetLeft by transition.animateDp(transitionSpec = {
        when {
            MyState.Normal isTransitioningTo MyState.Expand ->// 当从MyState.Normal到MyState.Expand的过程
                // 弹性动画,移动的较慢
                spring(stiffness = Spring.StiffnessVeryLow)
            else ->
                // 指定动画的执行事件,移动的较快
                spring(stiffness = Spring.StiffnessMedium)
        }
    }) { state ->
        // 从 0 到 contentWidth
        when (state) {
            MyState.Normal -> 0.dp
            MyState.Expand -> contentWidth
        }
    }

    // 根据状态改变边框线右边边坐标
    // 右边的transitionSpec和左边相反
    val offsetRight by transition.animateDp(transitionSpec = {
        when {
            MyState.Normal isTransitioningTo MyState.Expand ->// 当从MyState.Normal到MyState.Expand的过程
                // 弹性动画,移动的较快
                spring(stiffness = Spring.StiffnessMedium)
            else ->
                // 指定动画的执行事件,移动的较慢
                spring(stiffness = Spring.StiffnessVeryLow)
        }
    }) { state ->
        // 从 contentWidth 到 contentWidth*2
        when (state) {
            MyState.Normal -> contentWidth
            MyState.Expand -> contentWidth * 2
        }
    }

    // 根据状态改变边框颜色
    val color by transition.animateColor { state ->
        when (state) {
            MyState.Normal -> Color.Red
            MyState.Expand -> Color.Green
        }
    }

    ConstraintLayout(
        Modifier
            .background(MaterialTheme.colorScheme.surface)
            .padding(5.dp)
    ) {
        val (row, box, btn) = createRefs()

        Row(
            modifier = Modifier.constrainAs(row) {
                top.linkTo(parent.top)
            }
        ) {
            androidx.compose.material.Text(
                "normal", modifier = Modifier
                    .size(contentWidth, contentHeight),
                textAlign = TextAlign.Center
            )
            androidx.compose.material.Text(
                "expand", modifier = Modifier
                    .size(contentWidth, contentHeight),
                textAlign = TextAlign.Center
            )
        }

        Box(
            modifier = Modifier
                .constrainAs(box) {
                    top.linkTo(row.top)
                }
                .offset(x = offsetLeft)
                .height(contentHeight)
                .width(offsetRight - offsetLeft)
                .fillMaxSize()
                .border(BorderStroke(1.dp, color), RoundedCornerShape(4.dp))
        )

        Button(
            modifier = Modifier.constrainAs(btn) {
                top.linkTo(box.bottom)
            }, onClick = {
                currentState = if (currentState == MyState.Normal) {
                    MyState.Expand
                } else {
                    MyState.Normal
                }
            }) {
            Text("click")
        }
    }
}

效果:

访问.gif
6.3 createChildTransition

Transition还可以通过createChildTransition()方法创建新的子Transition,当需要分离多个子组件用到的过渡时,可以通过该方式

@OptIn(ExperimentalTransitionApi::class)
@Preview
@Composable
fun MyTransition3() {
    var currentState by remember { mutableStateOf(MyState.Normal) }
    // 定义Transition对象
    val transition = updateTransition(currentState)

    val childTransitionState = transition.createChildTransition {
        MyState.Expand
    }

    val childTransitionBool = transition.createChildTransition {
        it == MyState.Expand
    }
}
6.4 rememberInfiniteTransition

rememberInfiniteTransition可以生成一个一直运行的Transition,方法和一般的Transition相同,使用时需要指定infiniteRepeatable

@Preview
@Composable
fun MyRememberInfiniteTransition() {
    val transition = rememberInfiniteTransition()

    val animateColor by transition.animateColor(
        initialValue = Color.Red,
        targetValue = Color.Green,
        animationSpec = infiniteRepeatable(
            animation = tween(1000),
            repeatMode = RepeatMode.Reverse
        )
    )

    Box(
        Modifier
            .background(animateColor, MaterialTheme.shapes.medium)
            .size(50.dp))
}

效果:

三、低级动画

关于低级动画的介绍可以查看官方文档:低级别动画
高级动画已经和compose进行了结合,而低级动画都是基于协程的API,也就是在使用过程中,我们需要手动启动协程,我们可以使用附带效应的LaunchedEffect()compose中启动一个协程,关于附带效应后续会详细介绍

1.Animation

Animation 是可用的最低级别的动画API,子类型有两种:TargetBasedAnimationDecayAnimation。除非你需要手动控制动画时间,否则建议使用基于这些类构建的更高级别动画 API,由于平时基本不会使用,这部分仅作了解即可

1.1 TargetBasedAnimation

TargetBasedAnimation 就是正常的执行动画,需要手动根据执行的时间获取动画值:

@Preview
@Composable
fun MyAnimation() {
    val basedAnimation = remember {
        TargetBasedAnimation(
            // 动画时间1s
            animationSpec = infiniteRepeatable(tween(1000)),
            // 值为float类型
            typeConverter = Float.VectorConverter,
            // 初始值200f
            initialValue = 200f,
            // 目标值400f
            targetValue = 400f,
        )
    }

    var width by remember {
        mutableStateOf(0.dp)
    }

    LaunchedEffect(basedAnimation) {
        val startTime = withFrameNanos { it }
        var playTime = 0L

        while (true) {
            playTime = withFrameNanos { it } - startTime

            width = Dp(basedAnimation.getValueFromNanos(playTime))
        }
    }

    Box(
        modifier = Modifier
            .width(width)// 宽度随动画值而变化
            .height(50.dp)
            .background(Color.Blue)
    )
}

效果:

1.2 DecayAnimation

DecayAnimation是执行动画过程会有衰减,下面会在Animatable中使用它

2.Animatable

Animatable为 Float 和 Color 提供开箱即用的支持,也可以通过TwoWayConverter对其他类型支持

2.1 基本使用
@Preview
@Composable
fun MyAnimatable() {
    val animatable = remember { Animatable(Color.Gray) }

    var ok by remember { mutableStateOf(false) }
    LaunchedEffect(ok) {
        animatable.animateTo(if (ok) Color.Green else Color.Red)
    }

    Box(
        modifier = Modifier
            .background(animatable.value)
            .size(50.dp)
            .clickable {
                ok = !ok
            }
    )
}

效果:

2.2 animateDecay

animateDecayDecayAnimation进行了封装,我们需要传入初始速度initialVelocity,以及对应的DecayAnimationSpecDecayAnimationSpec直接通过封装好的rememberSplineBasedDecay()获取即可

    suspend fun animateDecay(
        initialVelocity: T,// 初始速度
        animationSpec: DecayAnimationSpec<T>,// 衰减,估值器
        block: (Animatable<T, V>.() -> Unit)? = null
    )
@Preview
@Composable
fun MyDecayAnimation() {
    val anim = remember { Animatable(0.dp, Dp.VectorConverter) }
    val decaySpec = rememberSplineBasedDecay<Dp>()

    LaunchedEffect(anim) {
        delay(3000)
        anim.animateDecay(1000.dp, decaySpec)
    }

    Box(
        modifier = Modifier
            .padding(top = anim.value)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
    )
}

效果:

2.3 exponentialDecay

exponentialDecay以指数进行衰减,可以传入摩擦系数:

@Preview
@Composable
fun MyDecayAnimation() {
    val anim = remember { Animatable(0.dp, Dp.VectorConverter) }
    // val decaySpec = rememberSplineBasedDecay<Dp>()
    val exponentDecay = exponentialDecay<Dp>(frictionMultiplier = 0.9f)

    LaunchedEffect(anim) {
        delay(3000)
        anim.animateDecay(1000.dp, exponentDecay)
    }

    Box(
        modifier = Modifier
            .padding(top = anim.value)// 上边距随动画值而变化
            .width(50.dp)
            .height(50.dp)
            .background(Color.Blue)
    )
}

效果:

四、可绘制图形动画

1.Drawable动画

就是传统通过定义xml的方式的帧动画

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/ic_launcher_foreground"
        android:duration="100" />
    <item
        android:drawable="@drawable/ic_launcher_background"
        android:duration="100" />
</animation-list>

2.矢量图动画

矢量图动画可以参考官方文档:矢量图动画

由于需要配合SVG图片,这边不做展示

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容