Search code examples
android-jetpack-composeandroid-jetpackandroid-jetpack-compose-canvas

How can I draw below canvas as segmented like 0.7 of path in jetpack compose?


I want to animate stroke path but I couldn't find a way to animate as segmented. How can I draw below canvas as segmented like 0.7 of path from starting point in jetpack compose?

  Canvas(modifier = Modifier.size(150.dp)) {
    val path = Path()
    val width: Float = size.width
    val height: Float = size.height
    path.apply {
        moveTo(width / 2, height / 5)
        cubicTo(
            5 * width / 14, 0f,
            0f, height / 15,
            width / 28, 2 * height / 5
        )
        cubicTo(
            width / 14, 2 * height / 3,
            3 * width / 7, 5 * height / 6,
            width / 2, height
        )
        cubicTo(
            4 * width / 7, 5 * height / 6,
            13 * width / 14, 2 * height / 3,
            27 * width / 28, 2 * height / 5
        )
        cubicTo(
            width, height / 15,
            9 * width / 14, 0f,
            width / 2, height / 5
        )
    }
    drawPath(color = Color.Red, path = path, style = Stroke(8f))
}

Solution

  • You can do it with val pathMeasure by remember { mutableStateOf(PathMeasure()) } and setting start and end distances with

    pathWithProgress.reset()
    
    pathMeasure.setPath(path, forceClosed = false)
    pathMeasure.getSegment(
        startDistance = 0f,
        stopDistance = pathMeasure.length * progress,
        pathWithProgress,
        startWithMoveTo = true
    )
    

    The snippet below starts from 0.7f of Path and based on progress, i used a Slider for demonstration but it can easily be aniamted, it can be drawn to full path length.

    Result

    enter image description here

    Code

    @Preview
    @Composable
    private fun Test() {
    
        var progress1 by remember {
            mutableStateOf(0.7f)
        }
    
        val transition: InfiniteTransition = rememberInfiniteTransition(label = "heart animation")
    
        // Infinite phase animation for PathEffect
        val progress2 by transition.animateFloat(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = infiniteRepeatable(
                animation = tween(
                    durationMillis = 1500,
                    easing = FastOutSlowInEasing
                ),
            ), label = "heart animation"
        )
    
    
        // This is the progress path which wis changed using path measure
        val pathWithProgress by remember {
            mutableStateOf(Path())
        }
    
        // using path
        val pathMeasure by remember { mutableStateOf(PathMeasure()) }
    
        val path = remember {
            Path()
        }
    
        Column(
            Modifier
                .fillMaxSize()
                .padding(20.dp)
        ) {
    
            Text(text = "Progress1: $progress1")
            Slider(
                value = progress1,
                onValueChange = { progress1 = it },
                valueRange = 0.7f..1f
            )
    
            Canvas(modifier = Modifier.size(150.dp)) {
                val width: Float = size.width
                val height: Float = size.height
    
                if (path.isEmpty) {
                    path.apply {
                        moveTo(width / 2, height / 5)
                        cubicTo(
                            5 * width / 14, 0f,
                            0f, height / 15,
                            width / 28, 2 * height / 5
                        )
                        cubicTo(
                            width / 14, 2 * height / 3,
                            3 * width / 7, 5 * height / 6,
                            width / 2, height
                        )
                        cubicTo(
                            4 * width / 7, 5 * height / 6,
                            13 * width / 14, 2 * height / 3,
                            27 * width / 28, 2 * height / 5
                        )
                        cubicTo(
                            width, height / 15,
                            9 * width / 14, 0f,
                            width / 2, height / 5
                        )
                    }
                }
    
                pathWithProgress.reset()
    
                pathMeasure.setPath(path, forceClosed = false)
                pathMeasure.getSegment(
                    startDistance = 0f,
                    stopDistance = pathMeasure.length * progress1,
                    pathWithProgress,
                    startWithMoveTo = true
                )
    
                drawPath(color = Color.Red, path = pathWithProgress, style = Stroke(8f))
    
                pathWithProgress.reset()
    
                pathMeasure.setPath(path, forceClosed = false)
                pathMeasure.getSegment(
                    startDistance = 0f,
                    stopDistance = pathMeasure.length * progress2,
                    pathWithProgress,
                    startWithMoveTo = true
                )
    
                translate(left = 500f) {
                    drawPath(color = Color.Blue, path = pathWithProgress, style = Stroke(8f))
                }
    
            }
        }
    }