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
)
}