如何在 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。
问题
我正在通过 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。