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 "反向")
        }
    }
}
相关推荐
alexhilton2 天前
Kotlin互斥锁(Mutex):协程的线程安全守护神
android·kotlin·android jetpack
是六一啊i3 天前
Compose 在Row、Column上使用focusRestorer修饰符失效原因
android jetpack
用户060905255224 天前
Compose 主题 MaterialTheme
android jetpack
用户060905255225 天前
Compose 简介和基础使用
android jetpack
用户060905255225 天前
Compose 重组优化
android jetpack
行墨5 天前
Jetpack Compose 深入浅出(一)——预览 @Preview
android jetpack
alexhilton6 天前
突破速度障碍:非阻塞启动画面如何将Android 应用启动时间缩短90%
android·kotlin·android jetpack
Pika7 天前
深入浅出 Compose 测量机制
android·android jetpack·composer
fundroid9 天前
掌握 Compose 性能优化三步法
android·android jetpack