https://kotlinlang.org logo
#compose
Title
# compose
s

Stylianos Gakis

04/09/2024, 12:54 PM
Is there a way for me to make a composable which will take up as much space it should take, think
Text("HHHHHH")
. But then have it somehow not render itself on the screen, so nothing is drawn at that area. But after that I still want to draw something in its place, with the exact same size? More in thread:
I can achieve exactly what I want with this
Copy code
Box(Modifier.padding(horizontal = 16.dp)) {
  Text(
    "HHHHHHHHHHHHHHHHHH",
    modifier = Modifier
      .layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)
        layout(placeable.width, placeable.height) {}
      },
  )
  Box(
    Modifier
      .matchParentSize()
      .debugBorder()
      .placeholder(true, highlight = PlaceholderHighlight.shimmer()),
  )
}
I take as much space as “HHHHHHHHHHHHHHHHHH”, and then I draw the second item in this box by doing
matchParentSize()
I thought I would be able to do this with something simpler like
Copy code
Text(
  "HHHHHH",
  modifier = Modifier
    .layout { measurable, constraints ->
      val placeable = measurable.measure(constraints)
      layout(placeable.width, placeable.height) {}
    }
    //.alpha(0f) or this
    .drawBehind { ... }
    .placeholder(true, highlight = PlaceholderHighlight.shimmer()),
)
But after I do the
layout
to not place anything there, (or even with an alpha(0f)) to hide everything at that point and then still be able to draw on it. But I can’t get it working so far at least
z

Zach Klippenstein (he/him) [MOD]

04/09/2024, 1:09 PM
AFAIK there’s no built in layout that just uses one child to measure another like this.
I would probably just write a custom layout to keep it a bit cleaner
s

Stylianos Gakis

04/09/2024, 1:21 PM
Hmm yeap, I think I’ll just do the same, since there’s no modifier chain trick I can do to make it happen 😄 Just wrote this (Begs for a better name)
Copy code
@Composable
fun LayoutWithoutRendering(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
  Layout(content = content, modifier = modifier) { measurables, constraints ->
    val placeable = measurables.first().measure(constraints)
    layout(placeable.width, placeable.height) {}
  }
}
And with call-site:
Copy code
LayoutWithoutRender(
  modifier = Modifier
    .padding(horizontal = 16.dp)
    .drawBehind { drawRect(Color.Red) }
) {
  Text(text = "HHHHHHHHHHHHHHHHHH")
}
And this does exactly what I was looking for actually. Since the modifier is added on the layout itself, I just don’t place the content. but measure it and adjust the layout size to it. The only thing this does wrong is that if you write more than one thing in the content block it will just not pick that up, but that should be fine. If I am not giving a BoxScope or ColumnScope or whatever they should not pass more than one anyway
FWIW I could also do
Copy code
Layout(content = { Box { content() } }, modifier = modifier) { measurables, constraints ->
To wrap it all in a box, and have more than one item be a bit of undefined behavior, or even pass BoxScope to it so it’s more “defined” 🤷
1
a

Albert Chang

04/09/2024, 1:27 PM
We created a modifier for this. The modifier contains two modifier nodes, one
LayoutModifierNode
hides the child, and the other
DrawModifierNode
draws the placeholder. The reason we use two nodes is that the
LayoutModifierNode
need to be applied after the
DrawModifierNode
, which I don’t think is possible with a single modifier node.
I believe a modifier is much easier to use than a layout.
s

Stylianos Gakis

04/09/2024, 1:30 PM
Hmm interesting, are the internals of that modifier something you are able to share? I haven't tried out the modifier node apis yet so I am afraid I would fumble hard trying to build this right now
z

Zach Klippenstein (he/him) [MOD]

04/09/2024, 1:38 PM
I don’t understand why you’d need two modifier nodes if you’re doing all the calculation and drawing by hand.
For the original case, you could also use TextMeasurer to have a single modifier measure your drawn content based on the text measurement.
a

Albert Chang

04/09/2024, 1:46 PM
Oh sorry I checked the code and found that we are actually setting the alpha of the original content (because we want a cross fade effect), so it’s not technically equal to not placing the content.
s

Stylianos Gakis

04/09/2024, 1:49 PM
re: Text measurer idea Yes I started looking at this https://medium.com/androiddevelopers/problem-solving-in-compose-text-d1dd1feafe4a as a first idea for inspiration and it looked to me that just placing but not showing anything was simple enough that I wanted to go this approach before I try anything more involved
a

Albert Chang

04/09/2024, 1:49 PM
I don’t understand why you’d need two modifier nodes
Because we wanted something like this:
Copy code
Modifier
    .drawWithContent {
        drawContent()
        ...
    }
    .layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)
        layout(placeable.width, placeable.height) {
            placeable.placeWithLayer(0, 0) {
                alpha = someAlpha
            }
        }
    }
I know I can use
drawContext.canvas.withSaveLayer(...) { drawContent() }
but I'm not sure if it's as efficient.
r

Ruckus

04/09/2024, 2:44 PM
Begs for a better name
Perhaps
Holdout
?
s

Stylianos Gakis

04/09/2024, 2:45 PM
Out of context Holdout would not make sense for me unfortunately 👀
r

Ruckus

04/09/2024, 2:49 PM
In that case, perhaps something along the line of
ReserveSpace
?
If not, I guess you could try asking in #naming 🙂
👍 1
71 Views