I have a tough task in hand to achieve in Jetpack Compose. The idea is,
I have a Row, that has two Image views as children. And when I load the images, one image might be shorter than the other one, and the row does not look symmetric when that happens. Here is how it looks:

Here is my Code:
(blocks below is basically objects with n image urls)
Row(
modifier = Modifier
.fillMaxWidth()
) {
displayDetails.blocks.forEach { itemIndex ->
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data("SomeUrl")
.size(Size.ORIGINAL)
.placeholder(R.drawable.placeholder)
.build(),
contentScale = ContentScale.FillWidth,
)
Image(
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
painter = painter,
contentDescription = null,
)
}
}
}
I would like to somehow achieve setting the row height to the minimum image height (and if possible, just crop the bigger one) however this doesnt seem possible since we dont know the height before we load the image.
Any help is appreciated, cheers.
p.s. I also have the image height and width returning from the API for these images however, I dont know how the device resolution is going to treat those width/heights, so i can not use them as static width heights. it always leaves empty spaces on top and bottom of images.
This is a pretty interesting question. IntrinsicSizes come from recursively from children Composable that are set before composing children, sort of placeholder.
You need to subcompose again, measure your child Composable with smaller height with max height for them to have equal heights.
I created 2 samples using same SubcomposeLayout i built.
@Composable
private fun ImageSubcomposeRow(modifier: Modifier, content: @Composable () -> Unit) {
SubcomposeLayout(modifier = modifier) { constraints: Constraints ->
var subcomposeIndex = 0
var placeables: List<Placeable> = subcompose(subcomposeIndex++, content)
.map {
it.measure(constraints)
}
if (placeables.size == 2) {
val heightLeft = placeables.first().measuredHeight
val heightRight = placeables.last().measuredHeight
println(
"🚀 SubcomposeRow Left height: $heightLeft\n" +
"RIGHT height: $heightRight"
)
// Get maximum height
val maxHeight = heightLeft.coerceAtMost(heightRight)
val maxWidth = (heightLeft + heightRight).coerceAtMost(constraints.maxWidth)
// Remeasure every element using height of tallest item using it as min height
// and max width should be the half of the max width of ancestor to display both with same width
// images
placeables = subcompose(subcomposeIndex, content).map { measurable: Measurable ->
measurable.measure(
constraints.copy(
minHeight = maxHeight,
minWidth = 0,
maxWidth = constraints.maxWidth / 2
)
)
}
// We place our Composables side by side using first ones width for second ones
// position
var x = 0
layout(maxWidth, maxHeight) {
placeables.forEach { placeable: Placeable ->
placeable.placeRelative(x, 0)
x += placeable.width
}
}
} else {
layout(0, 0) {}
}
}
}
Example1
Create a mutableStateList, which can trigger recomposition when we add new items to it, and i put both painters on success then call SubcomposeLayout. This will display images when both are loaded
@Composable
private fun ImageExample() {
Column {
val blocks = listOf(
"https://64.media.tumblr.com/5c1b241725c877cfd7c2a83910530ad0/efd70ac460498fb4-da/s1280x1920/c70cfdf1403aac31a55ebff90604f381081d8557.jpg",
"https://64.media.tumblr.com/8da6ed3327f22f582a94131b8fca816a/efd70ac460498fb4-83/s2048x3072/c6aa09d50fa9ddb4ec67d2c779c7f3a425a551c3.jpg"
)
val imageList = remember {
mutableStateListOf<Painter>()
}
blocks.forEach { itemIndex ->
println("Item: $itemIndex")
rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(itemIndex)
.size(coil.size.Size.ORIGINAL)
.build(),
contentScale = ContentScale.FillWidth,
onSuccess = { state ->
imageList.add(state.painter)
}
)
}
if (imageList.size == 2) {
ImageSubcomposeRow(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(4 / 3f)
.border(3.dp, Color.Yellow)
) {
imageList.forEach { painter ->
Image(
modifier = Modifier
.fillMaxWidth()
.border(2.dp, Color.Cyan),
painter = painter,
contentScale = ContentScale.FillHeight,
contentDescription = null,
)
}
}
}
}
}
Second example is as in question
@Composable
private fun ImageExample2() {
Column {
val blocks = listOf(
"https://64.media.tumblr.com/5c1b241725c877cfd7c2a83910530ad0/efd70ac460498fb4-da/s1280x1920/c70cfdf1403aac31a55ebff90604f381081d8557.jpg",
"https://64.media.tumblr.com/8da6ed3327f22f582a94131b8fca816a/efd70ac460498fb4-83/s2048x3072/c6aa09d50fa9ddb4ec67d2c779c7f3a425a551c3.jpg"
)
ImageSubcomposeRow(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(4 / 3f)
.border(3.dp, Color.Yellow)
) {
blocks.forEach { itemIndex ->
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(itemIndex)
.size(coil.size.Size.ORIGINAL)
.build(),
contentScale = ContentScale.FillWidth,
)
Image(
modifier = Modifier
.fillMaxWidth()
.border(2.dp, Color.Cyan),
painter = painter,
contentScale = ContentScale.FillHeight,
contentDescription = null,
)
}
}
}
}
Test
Column(modifier = Modifier.fillMaxSize()) {
Text(text = "Example1")
ImageExample()
Text(text = "Example2")
ImageExample2()
}
Also, as third option, this question probably can be solved using calculating heights based on height ratios of painters after getting both as in first example then set height for both Image without SubcomposeLayout.
SubcomposeLayout is needed when you don't know dimensions of Composables in advance before you measure or one depends on others dimensions. With subcompose you measure again with required constraints to have Composables with required dimensions.
Result

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With