0

I have created a PieChart using Android Compose. I have added an animation to the chart that should trigger when a slice is clicked. However, the animation is only working on the first click.

Here is the relevant code:

@Composable
internal fun PieChart(
modifier: Modifier = Modifier,
colors: List<Color>,
inputValues: List<Float>,
textColor: Color = MaterialTheme.colorScheme.primary,
) {
val chartDegrees = 360f
var startAngle = 270f
val proportions = remember(inputValues) {
    inputValues.map {
        it * 100 / inputValues.sum()
    }
}
val angleProgress = remember(proportions) {
    mutableStateOf(
        proportions.map { prop ->
            chartDegrees * prop / 100
        }
    )
}

val clickedItemIndex = remember {
    mutableStateOf(-1)
}

val progressSize = mutableListOf<Float>()

LaunchedEffect(angleProgress.value) {
    progressSize.add(angleProgress.value.first())
    for (x in 1 until angleProgress.value.size) {
        progressSize.add(angleProgress.value[x] + progressSize[x - 1])
    }
}

val density = LocalDensity.current
val textFontSize = with(density) { 30.dp.toPx() }
val textPaint = remember {
    Paint().apply {
        color = textColor.toArgb()
        textSize = textFontSize
        textAlign = Paint.Align.CENTER
    }
}

Box(Modifier.padding(24.dp)) {
    BoxWithConstraints(
        modifier = modifier.fillMaxWidth(),
        contentAlignment = Alignment.Center
    ) {

        val canvasSize = min(constraints.maxWidth, constraints.maxHeight)
        val size = Size(canvasSize.toFloat(), canvasSize.toFloat())
        val canvasSizeDp = with(density) { canvasSize.toDp() }
        val sliceWidth = with(LocalDensity.current) { 20.dp.toPx() }
        val animatedWidth = remember { Animatable(initialValue = sliceWidth) }
        LaunchedEffect(clickedItemIndex.value) {
            if (clickedItemIndex.value != -1) {
                animatedWidth.animateTo(
                    targetValue = sliceWidth * 1.5f,
                    animationSpec = tween(durationMillis = 600),
                )
            }
        }

        Canvas(
            modifier = Modifier
                .size(canvasSizeDp)
                .pointerInput(inputValues) {
                    detectTapGestures { offset ->
                        val clickedAngle = touchPointToAngle(
                            width = canvasSize.toFloat(),
                            height = canvasSize.toFloat(),
                            touchX = offset.x,
                            touchY = offset.y,
                            chartDegrees = chartDegrees
                        )
                        progressSize.forEachIndexed { index, item ->
                            if (clickedAngle <= item) {
                                clickedItemIndex.value = index
                                return@detectTapGestures
                            }
                        }
                    }
                }
        ) {

            angleProgress.value.forEachIndexed { index, angle ->
                val strokeWidth =
                    if (index == clickedItemIndex.value) 
                        animatedWidth.value 
                    else sliceWidth
                drawArc(
                    color = colors[index],
                    startAngle = startAngle,
                    sweepAngle = angle,
                    useCenter = false,
                    size = size,
                    style = Stroke(width = strokeWidth)
                )
                startAngle += angle
            }

            if (clickedItemIndex.value != -1) {
                drawIntoCanvas { canvas ->
                    canvas.nativeCanvas.drawText(
                        "${proportions[clickedItemIndex.value].roundToInt()}%",
                        ((canvasSize / 2).toFloat()),
                        ((canvasSize / 2).toFloat()),
                        textPaint
                    )
                }
            }
        }
    }
  }
}

Does anyone know how to fix this issue so that the animation works on subsequent clicks?

Niyas
  • 717
  • 11
  • 18

1 Answers1

0

I guess I see the problem: you are animating to the targetValue = sliceWidth * 1.5f but your sliceWidth is always the same. So when you call animateTo for the first time - it is animating to a new value (sliceWidth * 1.5f) and animatedWidth.value is updated. When you call animateTo a second time - animatedWidth.value already is sliceWidth * 1.5f and animateTo actually is doing nothing. That's why you don't see any new animations.

H.Taras
  • 753
  • 4
  • 15