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

Bradleycorn

11/04/2021, 2:03 PM
Custom Layout Question (some sample code in the 🧵)… I’m writing a basic Grid composable that takes a number of columns and a content composable. To do the measuring of the child measurables, setting the width contraints is pretty straight forward. But I’m curious about the height constraints for each measurable. It seems like I should take into account the heights of previously measured items (well, rows), and calculate the max available remaining height (based on the overall maxHeight constraints passed into the layout) and then pass height constraints into the measure function for the current item to ensure that it fits within the overall space. Does that seem right or do I not need to worry about
Here’s some pseudo code:
Copy code
@Composable
fun Grid(columns: Int, modifier: Modifier = Modifier, content: @Composable ()->Unit) {
    Layout(content = content, modifier = modifier) { measurables, constraints ->

        val rows = (measurables.size / columns) + (measurables.size.rem(columns).coerceAtMost(1))

        val maxColWidth = constraints.maxWidth / columns
        val minColWidth = constraints.minWidth / columns

        val placeables = measurables.mapIndexed { index, measurable ->
            val column = index % columns

            
            //RIGHT HERE .. is this OK, or should I restrict the height constraints too,
            // based on the row that this item will be placed in, and the height of previously
            // measured rows?
            val cellConstraints = constraints.copy(minWidth = minColWidth, maxWidth = maxColWidth)
        }


        layout() {}
    }
}
so for example, say I call my
Grid
composable with 3 columns and some modifiers such that the maxHeight constraint is 1000, and the measureables list contains 12 items. I’m going to end up with 4 rows. Let’s say that all my items are roughly the same height, of 300. So, the first 3 rows will end up taking up 900 px of space, leaving only 100 for the last row. When I’m measuring the items to go in the last row, do I need to pass in 100 as the maxHeight constraint to the measure function on those items … Or can I leave it off (and if so, I presume that would just cause the items to get clipped when the ui is rendered?).
z

Zach Klippenstein (he/him) [MOD]

11/04/2021, 4:29 PM
It’s up to you and how you want the layout to behave. If a layout measures itself beyond its max constraints then it will automatically be cropped and centered in its parent. But a layout is also free to give whatever constraints it wants to its children and crop them or arrange them however it wants. So if you want your grid items to be able to adapt to the available space, you'll need to give them accurate constraints. If you want them to be ignorant, you can implement that too
b

Bradleycorn

11/04/2021, 6:04 PM
@Zach Klippenstein (he/him) [MOD] thanks. I think I follow. If I want:
a layout measures itself beyond its max constraints then it will automatically be cropped and centered in its parent.
then I would measure the children without any further constraints, and use the sum of the row heights as the layout height. Or if I want:
a layout is also free to give whatever constraints it wants to its children and crop them or arrange them however it wants.
then I would calculate and limit the height constraints when measuring the children, so they they all fit within the layout. Correct? I think for my grid I will go with a variation on the first option: … I’ll measure children without any further constraints, and then for the layout, I’ll use the sum of the row heights, but coerce that value to be within the bounds of the container constraints. If I understand everything correctly, that’ll result in a grid that is effectively laid out from top to bottom, and if the content is too tall, rows/items at the bottom will get clipped/cropped out. To me that makes the most sense for my use case. If I call:
Copy code
Grid( columns = 3,
   modifier = Modifier.height(40.dp)) {
        (1..100).forEach { Text("Item $it") }
}
My expectation would be that it renders the first row or 2 and then clips somewhere in/around the 3rd row, since I set the height for the Grid to be
40.dp
. I think what I said above would accomplish that. I would NOT want it show show me some middle section of the list (because I did the layout pass with a huge height), or make each Text get clipped to a super tiny size (because I limited the height of each child so that they would all fit within the 40.dp limit). I’ll play around with it and see what I get.
z

Zach Klippenstein (he/him) [MOD]

11/04/2021, 6:05 PM
A layout measures itself by passing values to the
layout()
function in the
MeasurePolicy
. If that value is outside the max constraints, then it gets cropped and centered. But the layout can also measure its children with larger constraints, then measure itself as max constraints (so it has control over its own cropping/alignment in parent), and place its children however it wants.
b

Bradleycorn

11/04/2021, 6:08 PM
yeah. I’m saying I’ll do:
Copy code
//measure all the measurables, then

val layoutHeight = constraints.constrainHeight(rowHeights.sum())
layout(width, layoutHeight) {
   //place the children
}
(
constrainHeight
basically is the same as
rowHeights.sum().coerceAtMost(constraints.maxHeight)
)
z

Zach Klippenstein (he/him) [MOD]

11/04/2021, 6:11 PM
right that makes sense
👍 1
b

Bradleycorn

11/04/2021, 7:57 PM
Hey look at that!
2 Views