Gijs Stokman
Gijs Stokman

Reputation: 173

How can I make LazyHorizontalGrid wrap its content height in Jetpack Compose

This is what I have to create in Jetpack Compose:
enter image description here

This seems like a job for a LazyHorizontalGrid but it tries to fill the height of the screen by default. I can give it a fixed height but letting it wrap content height seems to be impossible.

I tried letting it wrap content height by using the .wrapContentSize() modifier which did nothing and I tried setting it's height to the minimum intrinsic size but this is not supported.

    Column(modifier = modifier.verticalScroll(scrollState)) {
        LazyHorizontalGrid(
            rows = GridCells.Fixed(2),
            modifier = modifier.heightIn(max = 200.dp),
            contentPadding = PaddingValues(horizontal = 24.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            items(items = contentTypes) { contentType ->
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    modifier = Modifier
                        .background(color = LocalCustomColors.current.surface, shape = RoundedCornerShape(12.dp))
                        .padding(all = 8.dp)
                        .width(80.dp)
                ) {
                    GenericIcon(
                        drawableId = contentType.icon
                    )
                    Spacer(modifier = Modifier.height(4.dp))
                    Caption3Text(
                        text = stringResource(contentType.text),
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                }
            }
        }
    }

Upvotes: 2

Views: 166

Answers (1)

Gijs Stokman
Gijs Stokman

Reputation: 173

Not an answer but this is the workaround I created for now:

@Composable
fun <T> GenericHorizontalGrid(
    items: List<T>,
    rowCount: Int,
    visibleItemCount: Int,
    horizontalItemSpacing: Int,
    verticalItemSpacing: Int,
    horizontalContentPadding: Int,
    extraEndSpacing: Int,
    modifier: Modifier = Modifier,
    itemContent: @Composable (T, Modifier) -> Unit,
) {
    val scrollState = rememberScrollState()
    val screenWidth = LocalConfiguration.current.screenWidthDp
    val totalNegativeSpace = ((visibleItemCount - 1) * horizontalItemSpacing) + horizontalContentPadding + extraEndSpacing
    val itemWidth = (screenWidth - totalNegativeSpace) / visibleItemCount
    val chunkSize = ceil(items.size / rowCount.toFloat()).toInt()
    val rows: List<List<T>> = items.chunked(chunkSize)

    Column(
        modifier = modifier
            .horizontalScroll(scrollState)
            .padding(horizontal = horizontalContentPadding.dp)
    ) {
        rows.forEachIndexed { rowIndex, row ->
            Row {
                row.forEachIndexed { themeIndex, item ->
                    itemContent(
                        item,
                        Modifier.width(itemWidth.dp)
                    )
                    if (themeIndex < row.lastIndex) {
                        Spacer(modifier = Modifier.width(horizontalItemSpacing.dp))
                    }
                }
            }
            if (rowIndex < rows.lastIndex) {
                Spacer(modifier = Modifier.height(verticalItemSpacing.dp))
            }
        }
    }
}

This generic approach may not be lazy rendering items but in my case the item list wasn't very long. Here is how you can use this generic composable to achieve the desired result shown in the question:

@Composable
private fun DiscoverContentTypes(
    contentTypes: List<DiscoverContentType>,
    modifier: Modifier = Modifier
) {
    val screenWidth = LocalConfiguration.current.screenWidthDp

    GenericHorizontalGrid(
        items = contentTypes,
        rowCount = 2,
        visibleItemCount = when {
            screenWidth < 360 -> 2
            screenWidth < 800 -> 3
            else -> 5
        },
        horizontalItemSpacing = 12,
        verticalItemSpacing = 8,
        horizontalContentPadding = 24,
        extraEndSpacing = 48,
        modifier = modifier
            .padding(top = 4.dp)
    ) { contentType, itemModifier ->
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = itemModifier
                .background(color = LocalCustomColors.current.surface, shape = RoundedCornerShape(12.dp))
                .padding(all = 8.dp)
        ) {
            GenericIcon(
                drawableId = contentType.icon,
                tint = LocalCustomColors.current.darkModeAwareTextColor
            )
            Spacer(modifier = Modifier.height(4.dp))
            Caption3Text(
                text = stringResource(contentType.text)
            )
        }
    }
}

Should anyone find and post a proper solution I'll make sure to mark it as the correct answer.

Upvotes: 0

Related Questions