2

This is a share your knowledge, Q&A-style question about using Layout, different Constraints and placing based on size and other Composables' positions to achieve a grid that changes Composable size, position and adds a number if number of Composables are greater than 4.

Thracian
  • 43,021
  • 16
  • 133
  • 222

1 Answers1

1

To layout Composables based on their count Layout is required and need to select Constraints, a detailed answer about Constraints is available in this link, based on number of items and place them accordingly.

For Constraints selection when there are 2 items we need to pick half width and full height to have result in question. When there are 4 items we need to pick half width and half height.

When item count is 3 we need to use 2 constraints, 1 for measuring first 2 items, another one measuring the one that covers whole width

@Composable
private fun ImageDrawLayout(
    modifier: Modifier = Modifier,
    itemCount: Int,
    divider: Dp,
    content: @Composable () -> Unit
) {

    val spacePx = LocalDensity.current.run { (divider).roundToPx() }

    val measurePolicy = remember(itemCount, spacePx) {
        MeasurePolicy { measurables, constraints ->

            val newConstraints = when (itemCount) {
                1 -> constraints
                2 -> Constraints.fixed(
                    width = constraints.maxWidth / 2 - spacePx / 2,
                    height = constraints.maxHeight
                )
                else -> Constraints.fixed(
                    width = constraints.maxWidth / 2 - spacePx / 2,
                    height = constraints.maxHeight / 2 - spacePx / 2
                )
            }

            val gridMeasurables = if (itemCount < 5) {
                measurables
            } else {
                measurables.take(3) + measurables.first { it.layoutId == "Text" }
            }

            val placeables: List<Placeable> = if (measurables.size != 3) {
                gridMeasurables.map { measurable: Measurable ->
                    measurable.measure(constraints = newConstraints)
                }
            } else {
                gridMeasurables
                    .take(2)
                    .map { measurable: Measurable ->
                        measurable.measure(constraints = newConstraints)
                    } +
                        gridMeasurables
                            .last()
                            .measure(
                                constraints = Constraints.fixed(
                                    constraints.maxWidth,
                                    constraints.maxHeight / 2 - spacePx
                                )
                            )
            }

            layout(constraints.maxWidth, constraints.maxHeight) {
                when (itemCount) {
                    1 -> {
                        placeables.forEach { placeable: Placeable ->
                            placeable.placeRelative(0, 0)
                        }
                    }
                    2 -> {
                        var xPos = 0
                        placeables.forEach { placeable: Placeable ->
                            placeable.placeRelative(xPos, 0)
                            xPos += placeable.width + spacePx
                        }
                    }
                    else -> {
                        var xPos = 0
                        var yPos = 0

                        placeables.forEachIndexed { index: Int, placeable: Placeable ->
                            placeable.placeRelative(xPos, yPos)

                            if (index % 2 == 0) {
                                xPos += placeable.width + spacePx
                            } else {
                                xPos = 0
                            }

                            if (index % 2 == 1) {
                                yPos += placeable.height + spacePx
                            }
                        }
                    }
                }
            }
        }
    }

    Layout(
        modifier = modifier,
        content = content,
        measurePolicy = measurePolicy
    )
}

Another thing to note here is we need to find find Composable that contains Text. It's possible to find it from index since it's 4th item but i used Modifier.layoutId() for demonstration. This Modifier helps finding Composables when you don't know in which order they are placed inside a Composaable.

val gridMeasurables = if (size < 5) {
    measurables
} else {
    measurables.take(3) + measurables.first { it.layoutId == "Text" }
}

And place items based on item count and we add space only after first item on each row.

Usage

@Composable
fun GridImageLayout(
    modifier: Modifier = Modifier,
    thumbnails: List<Int>,
    divider: Dp = 2.dp,
    onClick: ((List<Int>) -> Unit)? = null
) {
    if (thumbnails.isNotEmpty()) {

        ImageDrawLayout(
            modifier = modifier
                .clickable {
                    onClick?.invoke(thumbnails)
                },
            divider = divider,
            itemCount = thumbnails.size
        ) {
            thumbnails.forEach {
                Image(
                    modifier = Modifier.layoutId("Icon"),
                    painter = painterResource(id = it),
                    contentDescription = "Icon",
                    contentScale = ContentScale.Crop,
                )
            }

            if (thumbnails.size > 4) {
                val carry = thumbnails.size - 3
                Box(
                    modifier = Modifier.layoutId("Text"),
                    contentAlignment = Alignment.Center
                ) {
                    Text(text = "+$carry", fontSize = 20.sp)
                }
            }
        }
    }
}

And using GridImageLayout

Column {

val icons5 = listOf(
    R.drawable.landscape1,
    R.drawable.landscape2,
    R.drawable.landscape3,
    R.drawable.landscape4,
    R.drawable.landscape5,
    R.drawable.landscape6,
    R.drawable.landscape7,
)


GridImageLayout(
    modifier = Modifier
        .border(1.dp, Color.Red, RoundedCornerShape(10))
        .clip(RoundedCornerShape(10))
        .size(150.dp),
    thumbnails = icons5
)

}

Thracian
  • 43,021
  • 16
  • 133
  • 222