Row IntrinsicSize.Min not working when the children are async loading images
Asked Answered
T

1

3

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:

enter image description here

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.

Tearful answered 24/7, 2022 at 16:41 Comment(2)
#69663694 If you can provide links for downloading images i might be able to provide an answer as wellRichelle
@Richelle 64.media.tumblr.com/5c1b241725c877cfd7c2a83910530ad0/… and 64.media.tumblr.com/8da6ed3327f22f582a94131b8fca816a/…Tearful
R
3

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

enter image description here

Richelle answered 25/7, 2022 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.