Sparsh Dutta
Sparsh Dutta

Reputation: 3000

Recomposition of Canvas in Jetpack Compose

I'm using Jetpack Compose to implement my requirement of drawing a 'Gradual growing line', i.e a line that starts from a specified start and point and gradually 'grows' until it reaches a specified end point. I had earlier implemented this with Custom view which worked fine. My logic requires the 'onDraw()' to be re-called again and again based on some condition. For this, I had used 'invalidate()' while using Custom view. But now I'm using Jetpack Compose and unable to find a way to recompose 'Canvas'.

Here is my Jetpack compose code for 'gradual growing line':

   @Composable
        fun SplashUI() {
            var test = remember { mutableStateOf(0) }
                Canvas(modifier = Modifier.fillMaxSize()) {
                    //  starting point
                    x1 = 0.0;
                    y1 = size.height / 2.0;

                    //  ending point
                    x2 = size.width.toDouble()
                    y2 = size.height / 2.0;

                    divideLineIntoEqualParts();

                    if (test.value < listOfPoints.size) {
                        drawLine(
                            Color.Black,
                            Offset(
                                listOfPoints.get(0)?.x!!,
                                listOfPoints.get(0)?.y!!
                            ),
                            Offset(
                                listOfPoints.get(inte)?.x!!,
                                listOfPoints.get(inte)?.y!!
                            ),
                            strokeWidth = 5f
                        );
                    }
                    test.value = test.value + 1 //This doesn't trigger recomposition of canvas
                    //Recomposition of Canvas should happen after the above step for my logic to work

                   //I had implemented this earlier using custom view, and used 'invalidate()' like:
                   /* if (inte < listOfPoints.size) {
                        invalidate()
                    }*/
            }
        }


    private fun divideLineIntoEqualParts() {
        listOfPoints.clear()
        for (k in 1..50) {
            listOfPoints.add(PointF((x1 + k * (x2 - x1) / 50).toFloat(),
                (y1 + k * (y2 - y1) / 50).toFloat()
            ))
        }
        Log.d("listOfPoints : size : ", listOfPoints.size.toString() + "")
    }

Please suggest a way I can recompose Canvas, or some other way to make my logic work.

Upvotes: 5

Views: 8434

Answers (3)

mindlid
mindlid

Reputation: 2656

To emulate a timed game loop you can use the withFrame function family

LaunchedEffect(Unit) {
    while (true) {
        withFrameNanos {
            game. Update(it)
        }
    }
}

Source

Upvotes: 2

Waqas Tahir
Waqas Tahir

Reputation: 8252

I agree with Gabriele Mariotti's answer but I've found sometimes you need the control so to be able to redraw any time you want in canvas !

I was trying to develop an app for drawing and when the touch event happens I have to invalidate !

In this article Simple Jetpack Drawing App The canvas was invalidated by the change of MotionEvent's action so everytime the action of the motion event changes the canvas can be invalidated (redrawn)

We can use the same logic

var invalidations by remember{
    mutableStateOf(0)
}

Now in Canvas Composable

Canvas {
    invalidations.let{inv->
        //Draw Composables
    }
}

Now whenever you want to invalidate :

invalidations++

This is not the best way to do this since changing state of one variable causes only that composable to redrawn but invalidating this way will redraw anything that is drawn inside invalidations.let block !

Upvotes: 10

Gabriele Mariotti
Gabriele Mariotti

Reputation: 364211

There are some differences.
The code in the question works in an Android View, and you are drawing on the Canvas in the onDraw method. You are drawing an horizontal line dividing the available space (=width) in points.

In Compose you are just using a Canvas as a Composable, and you can animate the length of the line. Something like:

//Animation - it will be repeated 2 times
val animatedProgress = remember { Animatable(0.001f) }
LaunchedEffect(animatedProgress) {
    animatedProgress.animateTo(1f,
        animationSpec = repeatable(2,
            animation = tween(durationMillis = 3000, easing = LinearEasing)
        ))
}

Canvas(modifier = Modifier.fillMaxSize() ) {

    val startingPoint = Offset(0f, size.height / 2f)
    val endingPoint = Offset(size.width * animatedProgress.value, size.height / 2f)

    //At the end of animation just remove the line
    if (animatedProgress.isRunning) {
        drawLine(
            strokeWidth = 8.dp.toPx(),
            color = Color.Black,
            start = startingPoint,
            end = endingPoint
        )
    }
}

enter image description here

Upvotes: 2

Related Questions