Jetpack Compose 动画(自定义动画)

很多动画 API 通常接受用于自定义其行为的参数。

使用 AnimationSpec 参数自定义动画

大多数动画 API 允许开发者通过可选的 AnimationSpec 参数来自定义动画规范。

开发者可以使用不同类型的 AnimationSpec 来创建不同类型的动画。

使用 spring 创建基于物理特性的动画

spring 可在起始值和结束值之间创建基于物理特性的动画。它接受 2 个参数:dampingRatiostiffness

dampingRatio 定义弹簧的弹性。默认值为 Spring.DampingRatioNoBouncy

ts 复制代码
@Composable
private fun example1() {
    val enabled = remember { mutableStateOf(true) }

    val boxSize = 50

    val stiffnessList = arrayListOf(Spring.StiffnessVeryLow, Spring.StiffnessLow, Spring.StiffnessMediumLow, Spring.StiffnessMedium, Spring.StiffnessHigh)

    val selectedStiffness = remember {
        mutableStateOf(0)
    }

    val dampingRatioNoBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value  - dipToPx(boxSize) - dipToPx(15),
        label = "animateFloatAsState",
        animationSpec = spring(dampingRatio = DampingRatioNoBouncy, stiffness = stiffnessList[selectedStiffness.value])
    )
    val dampingRatioLowBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value  - dipToPx(boxSize) - dipToPx(15),
        label = "animateFloatAsState",
        animationSpec = spring(dampingRatio = DampingRatioLowBouncy, stiffness = stiffnessList[selectedStiffness.value])
    )
    val dampingRatioMediumBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value  - dipToPx(boxSize) - dipToPx(15),
        label = "animateFloatAsState",
        animationSpec = spring(dampingRatio = DampingRatioMediumBouncy, stiffness = stiffnessList[selectedStiffness.value])
    )
    val dampingRatioHighBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value  - dipToPx(boxSize) - dipToPx(15),
        label = "animateFloatAsState",
        animationSpec = spring(dampingRatio = DampingRatioHighBouncy, stiffness = stiffnessList[selectedStiffness.value])
    )
    Column(modifier = Modifier.fillMaxWidth()) {
        Box(
            Modifier
                .padding(top = 15.dp)
                .size(boxSize.dp)
                .graphicsLayer(translationX = dampingRatioNoBouncyTranslationX.value)
                .background(Color.Red)
        )
        Text(text = "DampingRatioNoBouncy", modifier = Modifier.padding(all = 15.dp))
        Box(
            Modifier
                .padding(top = 15.dp)
                .size(boxSize.dp)
                .graphicsLayer(translationX = dampingRatioLowBouncyTranslationX.value)
                .background(Color.Red)
        )
        Text(text = "DampingRatioLowBouncy", modifier = Modifier.padding(all = 15.dp))
        Box(
            Modifier
                .padding(top = 15.dp)
                .size(boxSize.dp)
                .graphicsLayer(translationX = dampingRatioMediumBouncyTranslationX.value)
                .background(Color.Red)
        )
        Text(text = "DampingRatioMediumBouncy", modifier = Modifier.padding(all = 15.dp))
        Box(
            Modifier
                .padding(top = 15.dp)
                .size(boxSize.dp)
                .graphicsLayer(translationX = dampingRatioHighBouncyTranslationX.value)
                .background(Color.Red)
        )
        Text(text = "DampingRatioHighBouncy", modifier = Modifier.padding(all = 15.dp))

        Text(text = "当前 stiffness (移动速度):${when(selectedStiffness.value) {
            0-> "StiffnessVeryLow"
            1-> "StiffnessLow"
            2-> "StiffnessMediumLow"
            3-> "StiffnessMedium"
            4-> "StiffnessHigh"
            else -> "StiffnessVeryLow"
        }}", modifier = Modifier.padding(all = 15.dp))

        Slider(
            value = selectedStiffness.value.toFloat(),
            onValueChange = { selectedStiffness.value = it.toInt() },
            modifier = Modifier.padding(horizontal = 15.dp),
            steps = stiffnessList.size - 2,
            valueRange = 0f..stiffnessList.size - 1f
        )
        Button(modifier = Modifier.padding(horizontal = 15.dp), onClick = {
            enabled.value = !enabled.value
        }) {
            Text(text = if (enabled.value) "正向" else "反向")
        }
    }
}

使用 tween 通过加/​减速曲线在起始值和结束值之间添加动画效果

ts 复制代码
@Composable
private fun example2() {
    val enabled = remember { mutableStateOf(true) }

    val boxSize = 50

    val x1 = remember { mutableStateOf(0f) }
    val y1 = remember { mutableStateOf(0f) }
    val x2 = remember { mutableStateOf(1f) }
    val y2 = remember { mutableStateOf(1f) }

    val easingList = arrayListOf(FastOutSlowInEasing,LinearOutSlowInEasing, FastOutLinearInEasing, LinearEasing, CubicBezierEasing(x1.value, y1.value, x2.value, y2.value))

    val selectedEasing = remember {
        mutableStateOf(0)
    }

    val canvasSize = 200
    val canvasSizeDp = dipToPx(dp = canvasSize)

    val canvasPointSize = 20f

    val translationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value  - dipToPx(boxSize) - dipToPx(15),
        label = "animateFloatAsState",
        animationSpec = tween(durationMillis  = 1000, easing = easingList[selectedEasing.value])
    )
    Column(modifier = Modifier.fillMaxWidth()) {
        Text(text = "tween 动画", modifier = Modifier.padding(all = 15.dp))
        Box(
            Modifier
                .padding(top = 15.dp)
                .size(boxSize.dp)
                .graphicsLayer(translationX = translationX.value)
                .background(Color.Red)
        )
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 0 }) {
            RadioButton(selected = selectedEasing.value == 0, onClick = { selectedEasing.value = 0 })
            Text(text = "FastOutSlowInEasing")
        }
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 1 }) {
            RadioButton(selected = selectedEasing.value == 1, onClick = { selectedEasing.value = 1 })
            Text(text = "LinearOutSlowInEasing")
        }
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 2 }) {
            RadioButton(selected = selectedEasing.value == 2, onClick = { selectedEasing.value = 2 })
            Text(text = "FastOutLinearInEasing")
        }
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 3 }) {
            RadioButton(selected = selectedEasing.value == 3, onClick = { selectedEasing.value = 3 })
            Text(text = "LinearEasing")
        }
        Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 4 }) {
            RadioButton(selected = selectedEasing.value == 4, onClick = { selectedEasing.value = 4 })
            Text(text = "CubicBezierEasing")
        }
        if (selectedEasing.value == 4) {
            Canvas(modifier = Modifier
                .padding(start = 15.dp)
                .size(canvasSize.dp), onDraw = {
                drawRect(color = Color.Gray)
                drawCircle(color = Color.Red, radius = canvasPointSize, center = Offset((x1.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize),
                    (canvasSizeDp - y1.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize)))
                drawCircle(color = Color.Blue, radius = canvasPointSize, center = Offset((x2.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize),
                    (canvasSizeDp - y2.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize)))
            })
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text(text = "x1=%.1f".format(x1.value), modifier = Modifier.padding(start = 15.dp))
                Slider(value = x1.value, onValueChange = { x1.value = it }, modifier = Modifier
                    .width(200.dp)
                    .padding(start = 15.dp))
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text(text = "y1=%.1f".format(y1.value), modifier = Modifier.padding(start = 15.dp))
                Slider(value = y1.value, onValueChange = { y1.value = it }, modifier = Modifier
                    .width(200.dp)
                    .padding(start = 15.dp))
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text(text = "x2=%.1f".format(x2.value), modifier = Modifier.padding(start = 15.dp))
                Slider(value = x2.value, onValueChange = { x2.value = it }, modifier = Modifier
                    .width(200.dp)
                    .padding(start = 15.dp))
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text(text = "y2=%.1f".format(y2.value), modifier = Modifier.padding(start = 15.dp))
                Slider(value = y2.value, onValueChange = { y2.value = it }, modifier = Modifier
                    .width(200.dp)
                    .padding(start = 15.dp))
            }
        }
        Button(modifier = Modifier.padding(horizontal = 15.dp), onClick = {
            enabled.value = !enabled.value
        }) {
            Text(text = if (enabled.value) "正向" else "反向")
        }
    }
}
相关推荐
Lei活在当下2 天前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计
天花板之恋2 天前
Compose之图片加载显示
android jetpack
消失的旧时光-19433 天前
Kotlinx.serialization 使用讲解
android·数据结构·android jetpack
Tans53 天前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Lei活在当下4 天前
【业务场景架构实战】2. 对聚合支付 SDK 的封装
架构·android jetpack
Tans56 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
alexhilton7 天前
runBlocking实践:哪里该使用,哪里不该用
android·kotlin·android jetpack
Tans510 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
ljt272496066111 天前
Compose笔记(四十九)--SwipeToDismiss
android·笔记·android jetpack
4z3313 天前
Jetpack Compose重组优化:机制剖析与性能提升策略
性能优化·android jetpack