Compose Grid layout span rows/cols

118 Views Asked by At

How to leverage LazyVerticalStaggeredGrid (or something else) to create something like this?

enter image description here

I want everything to be scrollable. I will always have one Red and one Blue. The number of Green can be thousands. So when I scroll down, I will only have a column of green on the right and nothing on the left.

This is what I tried:

LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Fixed(2),
        verticalItemSpacing = 4.dp,
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        modifier = Modifier.fillMaxSize()
    ) {
        item(
            span = StaggeredGridItemSpan.FullLine
        ) {
            Box(
                modifier = Modifier
                    .height(100.dp)
                    .fillMaxWidth()
                    .background(Color.Red)
            )
        }
        item(
        ) {
            Box(modifier = Modifier.height(IntrinsicSize.Max)) {
                Box(
                    modifier = Modifier
                        .height(200.dp)
                        .fillMaxWidth()
                        .background(Color.Green)
                )
            }
        }
        items(100) {
            Box(
                modifier = Modifier
                    .height(100.dp)
                    .fillMaxWidth()
                    .background(Color.Blue)
            )
        }
    }

But the green will place themselves below the Blue which is not the expected behavior.

Thanks!

1

There are 1 best solutions below

8
Thracian On

LazyVerticalStaggeredGrid positioning elements after other whether they are in item or items is expected behavior as with other LazyLists do the same.

Assigning Modifier.height(IntrinsicSize.Max) literally does nothing other than calling layout and measure twice in your case. It's useful when a Composable has child Composables that it can call layout phase twice to get intrinsic size calculations to determine which height to use.

https://developer.android.com/jetpack/compose/layouts/intrinsic-measurements

You can add only one element, which is LazyColumn, for blue section that is not measured with Constraints.Infinity. For that, you can either get maxHeight of screen via BoxWithConstraints or LocalConfiguration.current.screenHeightDp minus padding and height of red if you want and set a max height for a Constraints with not infinite maxHeight.

@Preview
@Composable
fun Test() {
    BoxWithConstraints {

        val height = maxHeight

        val screenHeight = LocalConfiguration.current.screenHeightDp

        LazyVerticalStaggeredGrid(
            columns = StaggeredGridCells.Fixed(2),
            verticalItemSpacing = 4.dp,
            horizontalArrangement = Arrangement.spacedBy(4.dp),
            modifier = Modifier.fillMaxSize()
        ) {
            item(
                span = StaggeredGridItemSpan.FullLine
            ) {
                Box(
                    modifier = Modifier
                        .height(100.dp)
                        .fillMaxWidth()
                        .background(Color.Red)
                )
            }
            item(
            ) {
                Box {
                    Box(
                        modifier = Modifier
                            .height(200.dp)
                            .fillMaxWidth()
                            .background(Color.Green)
                    )
                }
            }
            item {
                LazyColumn(
                    modifier = Modifier
                        .heightIn(max = height - 100.dp - 4.dp)
                        .border(2.dp, Color.Magenta),
                    verticalArrangement = Arrangement.spacedBy(4.dp)
                ) {
                    items(100) {
                        Box(
                            modifier = Modifier
                                .height(100.dp)
                                .fillMaxWidth()
                                .background(Color.Blue)
                        )
                    }
                }
            }
        }
    }
}

You can use Modifier.height instead of Modifier.heightIn if LazyColumn is supposed to occupy screenHeigh - padding - red element height independent of how many items it will have. heightIn makes sure that it will grow only up to this height.

Edit

If you wish to scroll left item as well you can assign height that is total height of blue rectangles + sum spaces between each item

@Preview
@Composable
private fun Test2() {

    val itemCount = 100
    val itemHeight = 100.dp
    val verticalItemSpacing = 4.dp
    val leftItemHeight = itemHeight * itemCount + verticalItemSpacing * (itemCount - 1)


    LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Fixed(2),
        verticalItemSpacing = verticalItemSpacing,
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        modifier = Modifier.fillMaxSize()
    ) {


        item(
            span = StaggeredGridItemSpan.FullLine
        ) {
            Box(
                modifier = Modifier
                    .height(100.dp)
                    .fillMaxWidth()
                    .background(Color.Red)
            )
        }
        item {
            Box(modifier = Modifier.height(leftItemHeight)) {
                Box(
                    modifier = Modifier
                        .height(200.dp)
                        .fillMaxWidth()
                        .background(Color.Green)
                )
            }
        }
        items(100) {
            Box(
                modifier = Modifier
                    .height(100.dp)
                    .fillMaxWidth()
                    .background(Color.Blue)
            ) {
                SideEffect {
                    println("Composing $it")
                }
            }
        }
    }
}