Hi everyone! Does `Modifier.layout()` work to only...
# compose
l
Hi everyone! Does
Modifier.layout()
work to only upper modifier chain, not below chain? I expect a green box to be drawn at a size of
100.dp
, and a blue box should be drawn over it. But the result was not match what I thought. Can someone explain how layout() modifier works? I comment out what I thought. Thanks in advance!
Copy code
Box(
    modifier = Modifier
        .size(100.dp) // 1) set size as 100.dp
        .background(Color.Green) // 2) set background color as green and a green box would be drawn as 100.dp
        .layout { measurable, constraints ->
            val m = measurable.measure(constraints)
            // 3) change size as (100.dp + 100).
            // So next things would be drawn (100.dp + 100) size. -> What I expected
            layout(m.width + 100, m.height + 100) {
                m.place(50, 50)
            }
        }
        .background(Color.Blue) // 4) based on upper layout() modifier, it would be drawn (100.dp + 100) size. -> What I expected
)
r
It seems like you aren't taking into account the passed in constraints with your calculations in the layout modifer, rather you are only looking at the items measurements but not the passed in constraints. If you look at the implementation of padding Modifier, it uses constraints.constrainWidth() and constraints.constrainHeight() to get the size of the item. This is your example with the padding modifiers implementation extracted into it..
Copy code
Box(modifier = Modifier
            .size(100.dp) // 1) set size as 100.dp
            .background(Color.Green) // 2) set background color as green and a green box would be drawn as 100.dp
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints.offset(-100, -100))

                val width = constraints.constrainWidth(placeable.width + 100)
                val height = constraints.constrainHeight(placeable.height + 100)
                layout(width, height) {
                    placeable.place(50, 50)
                }
            }
            .background(Color.Blue) // 4) based on upper layout() modifier, it would be drawn (100.dp + 100) size. -> What I expected
    )
Not sure if you've also seen this doc, but its useful for understanding more about constraints - https://developer.android.com/jetpack/compose/layouts/constraints-modifiers
l
Thanks! I will check the links you shared!
👀 1
u
It was helpful. However, I have a question. Shouldn't the background extension function be applied only once and only one color should come out? @Rebecca Franks 🥹
r
Nope, the modifier is applied twice in the chain of modifiers, its not like a size modifier that has one output constraints, its a drawing modifier, and each of those are drawing a background behind the content of the Box.
👍 2
l
@Rebecca Franks Thanks to you, I understand that I should use
constraints
in
layout() Modifier
to do the correct calculation. But I don’t understand why the modifier order doesn’t apply in the order I call it. First I called the
size() Modifier
to
100.dp
and then painted the green background. Then the green color should be painted at
100.dp
size, right? Could you explain why the size changed in the
layout() Modifier
is applied to the modifier above(
.background(Color.Green)
)? Shouldn’t the
layout() Modifier
be reflected in the
.background(Color.Blue)
below?
r
@leon I'm not sure i'm following, in the example I shared, the blue box is rendered smaller - so it is following the layout modifier, the modifier order video thats on the linked page goes through how to box the items. But the above example would be wrapping each modifier as follows, so the blue box is smaller than the green one.
l
@Rebecca Franks Thank you for continuing to reply. Everything works as expected in the code you wrote for me. But it doesn’t seem to work in order in the code I shared at first. If you have time, I would really appreciate it if you could run the code below. I added a red border box in size 100.dp for a guide in the code below. If you run the code, the green color is drawn larger than 100.dp. Shouldn’t green be drawn as much as 100.dp?
Copy code
Box(
    modifier = Modifier
        .fillMaxWidth()
        .aspectRatio(1f)
        .border(1.dp, Color.Black),
    contentAlignment = Alignment.Center
    //contentAlignment = Alignment.CenterStart
) {
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(Color.Green)
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                layout(placeable.width+ 100, placeable.height) {
                    placeable.place(0,0)
                }
            }
            .background(Color.Blue.copy(0.5f))
    )

    Box(
        modifier = Modifier
            .size(100.dp)
            .border(4.dp, Color.Red)
    )
}
In my mental model, the Modifier should act in the following order. 1.
size(100.dp)
-> I have 100.dp size now. 2.
background(Color.Green)
-> I need to paint it green, and I have 100.dp size, so I paint it as big as 100.dp 3.
layout { … }
-> I change the size bigger in the layout() Modifier. 4.
background(Color.Blue.copy(0.5f))
-> I need to paint it blue, and the size set now is bigger than 100.dp, so I paint it bigger. But the execution result is that the green is larger than the size 100.dp, and the blue is drawn in the size 100.dp. It’s not working in the order of my mental model that I wrote above. Please let me know if there is anything you don’t understand in my writing. Thank you so much for your help.
r
For step 3) you aren't correctly increasing the size of the constaints for the item to draw in,
size
modifier sets min and max constraints to the same values, (of 100.dp), now when you get into the layout modifier, your constraints that you are measuring the placeholder with, are still 100 by 100. so all its doing with
placeable.width + 100
, is moving the items around and not changing the size of the item, so you need to call measurable.measure() with constraints that have been updated to include the +100.
Copy code
@Preview
@Composable
fun Layout2Example() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f)
            .border(1.dp, Color.Green),
        contentAlignment = Alignment.Center
    ) {
        Box(
            modifier = Modifier
                .size(100.dp)
                .background(Color.Green)
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints.offset(100.dp.toPx().toInt()))
                    val width = constraints.constrainWidth(placeable.width)
                    val height = constraints.constrainHeight(placeable.height)
                    layout(width, height) {
                        placeable.place(0,0)
                    }
                }
                .background(Color.Blue.copy(0.5f))
        )
        Box(
            modifier = Modifier
                .size(100.dp)
                .border(4.dp, Color.Red)
        )
    }
}
Is this image the result of what you are actually looking for?
gratitude thank you 1
👍 1
l
@Rebecca Franks Yes! That image was the result I expected when I first wrote the code. I thought the Outer modifier could not be affected by the Inner modifier because, as you said, the Outer modifier boxes the Inner modifier. But in the code I first wrote, the Inner modifier seemed to affect the Outer modifier. So I was wondering if there were cases where the modifier’s influence was reversed. Thanks for your answers, I can conclude that if Constraint in the
layout()
modifier is not dealt with properly, it can affect the Outer modifier unintentionally. Thank you for your explanation!
r
It's affecting the overall size of the whole node, so changes to the layout could affect other parts up up the chain as it's part of the whole node.
👍 1
l
@Rebecca Franks Aha, thanks for letting me know! That is very useful information!
👍 1