Hi guys, I have a list which is coming from the se...
# compose
k
Hi guys, I have a list which is coming from the server. I am learning drawing on
canvas
. I don't know height of
canvas
. So I learnt basic how to measure text using
rememberTextMeasurer
with
TextLayoutResult
for single item and provide height on
canvas
. If I mock 5 item in the list, then how can we measure all text height at a time and provide in
canvas
? I am trying to put my
rememberTextMeasurer
and
TextLayoutResult
inside
movieList
of
forEach
it complains about.
Copy code
@OptIn(ExperimentalTextApi::class)
@Composable
fun TimeLineComposable() {
    Column(modifier = Modifier.fillMaxSize()) {
        val movieList = getMovieList()
        val heading = AnnotatedString("Welcome to heading")
        val titleTextMeasurer = rememberTextMeasurer()
        val titleTextLayoutResult = remember {
            titleTextMeasurer.measure(heading, TextStyle(fontSize = 16.sp, color = Color.White))
        }
        val canvasHeight = with(LocalDensity.current) {
            titleTextLayoutResult.size.height.toDp()
        }
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Black)
                .height(canvasHeight)
        ) {
            drawText(
                textLayoutResult = titleTextLayoutResult,
                topLeft = Offset(
                    x = center.x - titleTextLayoutResult.size.width / 2,
                    y = center.y - titleTextLayoutResult.size.height / 2,
                )
            )
        }
    }
}
Error
Copy code
@OptIn(ExperimentalTextApi::class)
@Composable
fun TimeLineComposable() {
    Column(modifier = Modifier.fillMaxSize()) {
        val movieList = getMovieList()
        val heading = AnnotatedString("Welcome to heading")
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Black)
                .height(canvasHeight)
        ) {
            movieList.forEach {
                val titleTextMeasurer = rememberTextMeasurer()
                val titleTextLayoutResult = remember {
                    titleTextMeasurer.measure(heading, TextStyle(fontSize = 16.sp, color = Color.White))
                }
                val canvasHeight = with(LocalDensity.current) {
                    titleTextLayoutResult.size.height.toDp()
                }
                drawText(
                    textLayoutResult = titleTextLayoutResult,
                    topLeft = Offset(
                        x = center.x - titleTextLayoutResult.size.width / 2,
                        y = center.y - titleTextLayoutResult.size.height / 2,
                    )
                )
            }
        }
    }
}
z
remember the text measurer in your composable, then call
measure
on it as many times as you need from in your canvas
note that measuring is expensive, you probably want to measure from the layout phase or the draw cache (using
drawWithCache
)
cc @Halil Ozercan in case i’m super wrong
k
Yes put the
rememberTextMeasurer
outside of canvas block
Now I want the canvas height, how can I find the height ?
z
Look at the receiver type of the Canvas lambda, check its properties
k
Sorry I mean how can I provide the height on canvas?
Copy code
@OptIn(ExperimentalTextApi::class)
@Composable
fun TimeLineComposable() {
    Column(modifier = Modifier.fillMaxSize()) {
        val movieList = getMovieList()
        val titleTextMeasurer = rememberTextMeasurer()
        var canvasHeight by remember { mutableStateOf(0.dp) }
        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Black)
                .height(canvasHeight)
        ) {
            movieList.forEach {
                val titleTextLayoutResult = titleTextMeasurer.measure(
                    AnnotatedString(it.showDay),
                    TextStyle(fontSize = 16.sp, color = Color.White)
                )
                canvasHeight = titleTextLayoutResult.size.height.toDp()
                drawText(
                    textLayoutResult = titleTextLayoutResult,
                    topLeft = Offset(
                        x = center.x - titleTextLayoutResult.size.width / 2,
                        y = center.y - titleTextLayoutResult.size.height / 2,
                    )
                )
            }
        }
    }
}
This is not working, because height didn't get it by
canvasHeight
Copy code
Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Black)
                .height(canvasHeight)
        ) {
z
How do you want to measure the canvas?
k
I want to measure based on list item size. That's why I used
Copy code
val titleTextLayoutResult = titleTextMeasurer.measure(
                    AnnotatedString(it.showDay),
                    TextStyle(fontSize = 16.sp, color = Color.White)
                )
                canvasHeight += titleTextLayoutResult.size.height.toDp()
z
ok, so then you need to use a layout modifier, make the measure calls in the measure pass, and calculate your layout from there. E.g.
Copy code
Modifier.layout { measurable, constraints ->
  // 1. Measure all your texts using textMeasurer.measure(…)
  // 2. Calculate height, width based on constraints and text measurement results
  //    e.g. val height = textBounds.sumOf { it.height }.coerceAtMost(constraints.maxHeight)
  // 3. Determine child constraints using that height and whatever you want for width.
  //    e.g. val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
  val placeable = measurable.measure(childConstraints)
  layout(placeable.width, placeable.height) {
    placeable.place(0, 0)
  }
}
but at that point you’re just reimplementing
Column
with a bunch of child `BasicText`s, so why not just do that?
k
Actually, I want to build more complex Ui then these. So I am exploring the use of all these.
I want to build timeline view in compose. This is my github code. I made it in canvas but there is a problem on there. My multiple view get overlap each other..
Is there any example how to use layout modifier in canvas ?
h

https://www.youtube.com/watch?v=xcfEQO0k_gU

You can refer to this video on how to implement a custom layout with a similar functionality https://github.com/android/compose-samples/tree/main/JetLagged This is the repo that contains the project from video.
k
Sure, thanks a lot.. i'll take a look now
a
Doesn’t canvas drawscope contain height and width info?
k
It will not give you an accurate height to draw textview.
a
Why not accurate? It is literally the size of the canvas in pixels
k
Because you don't know how much in pixel it will draw.
I am drawing on the basics of text height
a
You want to know the size of the text height, not the canvas
k
Yes
a
But in your first post you write: "I don't know height of
canvas
."
k
After that, I was correct in the next statement..