如何在 Android Canvas 中处理多个形状的动画?

How to handle animating multiple shapes in Android Canvas?

问题

我正在通过 Android 的 Jetpack Compose 使用 Canvas。我正在尝试绘制带动画的条形图,它在 first 动画之后运行良好。所以当启动应用程序时,除了底部数字(正确)外,屏幕上没有绘制任何内容。然后按下一个按钮改变状态,第一个条被绘制到屏幕上(正确)但它没有动画(不正确)。之后,所有状态更改都会正确设置动画。我不知道为什么。

尝试次数

我已经尝试了一些方法,我目前接近工作的解决方案是我想要制作动画的每个栏的动画变量。就像上面一样,在第一个条形图被绘制到屏幕后,它就起作用了。问题是我无法在第一个栏上设置动画。

代码示例

我想为此分享我的所有代码。这是为了学习,所以我认为可用的代码示例越好,就越有可能有人可以提供帮助,这个问题对其他人的帮助也越大。


@Composable
fun RollChart(rolls: Map<Int, Int>, modifier: Modifier) {
    Box(
        modifier = modifier

    ) {

        val animationTargetState = (0..10).map { index ->
            remember { mutableStateOf(0f) }
        }

        val animationValues = (0..10).map { index ->
            animateFloatAsState(
                targetValue = animationTargetState[index].value,
                animationSpec = tween(durationMillis = 1000),
            )
        }

        Canvas(modifier = Modifier.fillMaxSize()) {
            inset(horizontal = 50f, vertical = 48f) {
                val maxRole = rolls.maxOf { it.value }
                val width = (size.width / rolls.size)
                val heightScale = (size.height / maxRole)
                withTransform({
                    translate(left = 50f)
                }) {
                    rolls.forEach { (label, height) ->
                        animationTargetState[label - 2].value =
                            heightScale * height.toFloat()
                        bar(
                            "$label",
                            width,
                            label - 2,
                            animationValues[label - 2].value
                        )
                    }
                }
                val range = IntRange(0, maxRole).step(1 + (maxRole / 5))
                if (range.last == 0) {
                    numberLine("0", 0f)
                }
                for (it in range) {
                    numberLine(
                        "$it",
                        heightScale * it
                    )

                }
            }
        }
    }

}

val textPaint = Paint().asFrameworkPaint().apply {
    isAntiAlias = true
    textSize = 48f
    color = android.graphics.Color.BLACK
    typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD)
}

fun DrawScope.numberLine(
    label: String,
    height: Float
) {
    val base = size.height
    drawLine(
        color = Color.Black,
        start = Offset(0f, base - height),
        end = Offset(size.width, base - height),
        strokeWidth = 5f,
        alpha = 0.5f
    )
    drawIntoCanvas {
        it.nativeCanvas.drawText(
            "$label",
            0f,
            (base - height) - 5f,
            textPaint
        )
    }
}

fun DrawScope.bar(
    label: String,
    width: Float,
    roll: Int,
    height: Float
) {
    val xPos = width * roll.toFloat()
    val base = size.height

    drawRect(
        color = Purple700,
        topLeft = Offset(xPos, base),
        size = Size(width - 10, 0 - height)
    )
    drawIntoCanvas {
        it.nativeCanvas.drawText(
            "$label",
            xPos,
            base + 48f,
            textPaint
        )
    }
}

另外我觉得视频演示很有用,所以我把它放在这里。

所以,我意识到错误在这一行:

val heightScale = (size.height / maxRole)

一开始的maxRole为0,导致动画永远不会出现。如果有人可以回答 why,我将不胜感激,但我的想法是动画调用崩溃的原因是不可能的。然后在第一次渲染之后,所有 maxRole 值都 > 0。