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 "反向")
        }
    }
}
相关推荐
帅次10 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二2 天前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss4 天前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x02421 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton24 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss24 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳1 个月前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm