Android Jetpack Compose – 将图像缩放到其兄弟组件的大小

huangapple go评论68阅读模式
英文:

Android Jetpack Compose - scale image to the size of its sibling composable

问题

我想创建一个可组合项,其中包含一张图片和旁边的文本,使图片自动缩放以与旁边的文本高度相同。

我曾经这样做:

@Composable
fun Test(
) {
    Row(
        modifier = Modifier
            .height(IntrinsicSize.Min)
            .width(200.dp),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Image(
            painterResource(id = R.drawable.some_imported_svg),
            contentDescription = null,
            contentScale = ContentScale.Fit,
            modifier = Modifier
                .padding(end = 8.dp)
        )
        Text(
            modifier = Modifier,
            text = "Yum Yum",
            color = MaterialTheme.colors.onBackground,
        )
    }
}

它在我更新项目中的某些依赖项之前正常工作。经过一些调查,我觉得行为上的改变是在以下依赖项更新中进行的:

"androidx.compose.ui:ui"
"androidx.compose.material:material"
"androidx.compose.material:material-icons-core"
"androidx.compose.material:material-icons-extended"
"androidx.compose.ui:ui-tooling"
"androidx.compose.ui:ui-tooling-preview"

具体来说,是从版本1.2.0-alpha04之前的版本升级到该版本或更高版本时发生的变化。

例如:

使用1.1.1
Android Jetpack Compose – 将图像缩放到其兄弟组件的大小

使用1.4.3
Android Jetpack Compose – 将图像缩放到其兄弟组件的大小

由于这是一个简单的任务,并且那次更改很久以前就已经进行了,而且没有关于它的投诉(我找不到),我猜测我当时做的事情是错误的,只是起作用,现在我可能漏掉了其他一些东西。

非常感谢任何帮助!谢谢!

英文:

I want to make a composable that holds a an image and a text next to it in a way that the image will scale itself to be as high as the text next to it.
I used to do it like that:

@Composable
fun Test(
) {
    Row(
        modifier = Modifier
            .height(IntrinsicSize.Min)
            .width(200.dp),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Image(
            painterResource(id = R.drawable.some_imported_svg),
            contentDescription = null,
            contentScale = ContentScale.Fit,
            modifier = Modifier
                .padding(end = 8.dp)
        )
        Text(
            modifier = Modifier,
            text = "Yum Yum",
            color = MaterialTheme.colors.onBackground,
        )
    }
}

It worked up until I made an update to some of the dependencies in the project.
After some investigation, it seems to me that the behavioural change was made in one of the following dependencies updates:

"androidx.compose.ui:ui"
"androidx.compose.material:material"
"androidx.compose.material:material-icons-core"
"androidx.compose.material:material-icons-extended"
"androidx.compose.ui:ui-tooling"
"androidx.compose.ui:ui-tooling-preview"

More specifically, when updating from a version prior to 1.2.0-alpha04 to that version or to a newer one.
I.e.

With 1.1.1:
Android Jetpack Compose – 将图像缩放到其兄弟组件的大小

With 1.4.3:
Android Jetpack Compose – 将图像缩放到其兄弟组件的大小

Since it is a simple task to do and that change was made long time ago and there were no complains (that I found) about it, my guess is that What I did back then was wrong and just worked and now there is something else that I am missing.

Any help would be highly appreciated!
Thanks a lot!

答案1

得分: 2

I have translated the provided content:

  1. > Asking for intrinsics measurements doesn't measure the children twice.
    > Children are queried for their intrinsic measurements before they're

    measured and then, based on that information the parent calculates the
    constraints to measure its children with.
    Link to Android documentation

  2. > In the case of intrinsics, they are more of a tentative calculation in

    order to perform real measuring using the obtained values. Imagine a
    row with 3 children. In order to make its height match the height of
    the tallest child, it would need to get the intrinsic measures of all
    its children, and finally measure itself using the maximum one.
    Link to Android documentation

  3. Modifier.onSizeChanged or Modifier.globallyPositioned to get size to effect another Composable has risk of infinite recompositions. If you have Text you can use TextMeasured, as in this question if you know which one of the siblings need to be use as reference you can use Layout if you don't know which one should be bigger or lower you should use SubcomposeLayout. Link to Android documentation

  4. Link to Stack Overflow

  5. With TextMeasurer

    val textMeasurer = rememberTextMeasurer()
    
    val text = "Yum Yum"
    val textLayoutResult = remember {
        textMeasurer.measure(AnnotatedString(text))
    }
    
    val density = LocalDensity.current
    
    val height = with(density) {
        textLayoutResult.size.height.toDp()
    }
    
    Row(
        modifier = Modifier
            .border(1.dp, Color.Red)
            .height(height)
            .width(200.dp),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Image(
            painterResource(id = R.drawable.placeholder),
            contentDescription = null,
            contentScale = ContentScale.Fit,
            modifier = Modifier
                .border(2.dp, Color.Black)
                .padding(end = 8.dp)
        )
    
        Text(
            modifier = Modifier,
            text = text,
            color = MaterialTheme.colors.onBackground,
        )
    }
    
  6. With Layout that measures Text first and then measures Image with Text's height

    @Composable
    private fun ImageTextLayout(
        modifier: Modifier = Modifier,
        content: @Composable () -> Unit
    ) {
        Layout(
            modifier = modifier,
            content = content
        ) { measurables: List<Measurable>,
            constraints: Constraints ->
    
            require(measurables.size == 2)
    
            val textPlaceable = measurables.last().measure(constraints = constraints)
            val imagePlaceable = measurables.first().measure(
                constraints.copy(
                    minWidth = 0,
                    minHeight = textPlaceable.height,
                    maxHeight = textPlaceable.height
                )
            )
    
            val hasFixedWidth = constraints.hasFixedWidth
    
            val width = if (hasFixedWidth) constraints.maxWidth
            else imagePlaceable.width + textPlaceable.width
            val height = imagePlaceable.height.coerceAtMost(textPlaceable.height)
    
            layout(width, height) {
                imagePlaceable.placeRelative(0, 0)
                textPlaceable.placeRelative(imagePlaceable.width, 0)
            }
        }
    }
    
  7. Usage

    ImageTextLayout(
        modifier = Modifier
            .border(1.dp, Color.Green)
            .width(200.dp),
    ) {
        Image(
            painterResource(id = R.drawable.placeholder),
            contentDescription = null,
            contentScale = ContentScale.Fit,
            modifier = Modifier
                .border(2.dp, Color.Black)
                .padding(end = 8.dp)
        )
    
        Text(
            modifier = Modifier,
            text = text,
            color = MaterialTheme.colors.onBackground,
        )
    }
    
  8. Full Demo

    @OptIn(ExperimentalTextApi::class)
    @Preview
    @Composable
    fun Test(
    ) {
    
        Column {
    
            Spacer(modifier = Modifier.height(40.dp))
    
            val textMeasurer = rememberTextMeasurer()
    
            val text = "Yum Yum"
            val textLayoutResult = remember {
                textMeasurer.measure(AnnotatedString(text))
            }
    
            val density = LocalDensity.current
    
            val height = with(density) {
                textLayoutResult.size.height.toDp()
            }
    
            Row(
                modifier = Modifier
                    .border(1.dp, Color.Red)
                    .height(height)
                    .width(200.dp),
                horizontalArrangement = Arrangement.Start,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Image(
                    painterResource(id = R.drawable.placeholder),
                    contentDescription = null,
                    contentScale = ContentScale.Fit,
                    modifier = Modifier
                        .border(2.dp, Color.Black)
                        .padding(end = 8.dp)
                )
    
                Text(
                    modifier = Modifier,
                    text = text,
                    color = MaterialTheme.colors.onBackground,
                )
            }
    
            ImageTextLayout(
                modifier = Modifier
                    .border(1.dp, Color.Green)
                    .width(200.dp),
            ) {
                Image(
                    painterResource(id = R.drawable.placeholder),
                    contentDescription = null,
                    contentScale = ContentScale.Fit,
                    modifier = Modifier
                        .border(2.dp, Color.Black)
                        .padding(end = 8.dp)
                )
    
                Text(
                    modifier = Modifier,
                    text = text,
                    color = MaterialTheme.colors.onBackground,
                )
            }
        }
    }
    

I hope this helps!

英文:

> Asking for intrinsics measurements doesn't measure the children twice.
> Children are queried for their intrinsic measurements before they're
> measured and then, based on that information the parent calculates the
> constraints to measure its children with.
>

https://developer.android.com/jetpack/compose/layouts/intrinsic-measurements#intrinsics-in-action
> In the case of intrinsics, they are more of a tentative calculation in
> order to perform real measuring using the obtained values. Imagine a
> row with 3 children. In order to make its height match the height of
> the tallest child, it would need to get the intrinsic measures of all
> its children, and finally measure itself using the maximum one.

https://newsletter.jorgecastillo.dev/p/introducing-lookaheadlayout

Modifier.onSizeChanged or Modifier.globallyPositioned to get size to effect another Composable has risk of infinite recompositions. If you have Text you can use TextMeasured, as in this question if you know which one of the siblings need to be use as reference you can use Layout if you don't know which one should be bigger or lower you should use SubcomposeLayout.

https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/package-summary#(androidx.compose.ui.Modifier).onSizeChanged(kotlin.Function1)

https://stackoverflow.com/questions/73354911/how-to-get-exact-size-without-recomposition

For the scope of this question i will post first 2.

With TextMeasurer

val textMeasurer = rememberTextMeasurer()

val text = &quot;Yum Yum&quot;
val textLayoutResult = remember {
    textMeasurer.measure(AnnotatedString(text))
}

val density = LocalDensity.current

val height = with(density) {
    textLayoutResult.size.height.toDp()
}

Row(
    modifier = Modifier
        .border(1.dp, Color.Red)
        .height(height)
        .width(200.dp),
    horizontalArrangement = Arrangement.Start,
    verticalAlignment = Alignment.CenterVertically,
) {
    Image(
        painterResource(id = R.drawable.placeholder),
        contentDescription = null,
        contentScale = ContentScale.Fit,
        modifier = Modifier
            .border(2.dp, Color.Black)
            .padding(end = 8.dp)
    )

    Text(
        modifier = Modifier,
        text = text,
        color = MaterialTheme.colors.onBackground,
    )
}

With Layout that measures Text first and then measures Image with Text's height

@Composable
private fun ImageTextLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -&gt; Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables: List&lt;Measurable&gt;,
        constraints: Constraints -&gt;

        require(measurables.size == 2)

        val textPlaceable = measurables.last().measure(constraints = constraints)
        val imagePlaceable = measurables.first().measure(
            constraints.copy(
                minWidth = 0,
                minHeight = textPlaceable.height,
                maxHeight = textPlaceable.height
            )
        )

        val hasFixedWidth = constraints.hasFixedWidth

        val width = if (hasFixedWidth) constraints.maxWidth
        else imagePlaceable.width + textPlaceable.width
        val height = imagePlaceable.height.coerceAtMost(textPlaceable.height)

        layout(width, height) {
            imagePlaceable.placeRelative(0, 0)
            textPlaceable.placeRelative(imagePlaceable.width, 0)
        }
    }
}

Usage

ImageTextLayout(
    modifier = Modifier
        .border(1.dp, Color.Green)
        .width(200.dp),
) {
    Image(
        painterResource(id = R.drawable.placeholder),
        contentDescription = null,
        contentScale = ContentScale.Fit,
        modifier = Modifier
            .border(2.dp, Color.Black)
            .padding(end = 8.dp)
    )

    Text(
        modifier = Modifier,
        text = text,
        color = MaterialTheme.colors.onBackground,
    )
}

Full Demo

@OptIn(ExperimentalTextApi::class)
@Preview
@Composable
fun Test(
) {

    Column {

        Spacer(modifier = Modifier.height(40.dp))

        val textMeasurer = rememberTextMeasurer()

        val text = &quot;Yum Yum&quot;
        val textLayoutResult = remember {
            textMeasurer.measure(AnnotatedString(text))
        }

        val density = LocalDensity.current

        val height = with(density) {
            textLayoutResult.size.height.toDp()
        }

        Row(
            modifier = Modifier
                .border(1.dp, Color.Red)
                .height(height)
                .width(200.dp),
            horizontalArrangement = Arrangement.Start,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Image(
                painterResource(id = R.drawable.placeholder),
                contentDescription = null,
                contentScale = ContentScale.Fit,
                modifier = Modifier
                    .border(2.dp, Color.Black)
                    .padding(end = 8.dp)
            )

            Text(
                modifier = Modifier,
                text = text,
                color = MaterialTheme.colors.onBackground,
            )
        }

        ImageTextLayout(
            modifier = Modifier
                .border(1.dp, Color.Green)
                .width(200.dp),
        ) {
            Image(
                painterResource(id = R.drawable.placeholder),
                contentDescription = null,
                contentScale = ContentScale.Fit,
                modifier = Modifier
                    .border(2.dp, Color.Black)
                    .padding(end = 8.dp)
            )

            Text(
                modifier = Modifier,
                text = text,
                color = MaterialTheme.colors.onBackground,
            )
        }
    }
}

答案2

得分: 1

以下是翻译好的代码部分:

@Composable
fun Test(
) {
    Row(
        modifier = Modifier
            .height(IntrinsicSize.Min)
            .width(200.dp),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        val wantedHeight = remember { mutableStateOf(0) }

        Image(
            painterResource(id = R.drawable.ic_stages),
            contentDescription = null,
            modifier = Modifier
                .padding(end = 8.dp)
                .height(wantedHeight.value.dp)
        )
        Text(
            modifier = Modifier.onGloballyPositioned { coordinates ->
                wantedHeight.value = coordinates.size.height.toDp()
            },
            text = "Yum Yum",
            color = MaterialTheme.colors.onBackground,
        )
    }
}

请注意,强制重新组合可能会导致问题,您可能会陷入重新组合循环或性能问题。在这个简单的示例中,您不会遇到问题,但请考虑为图像提供一个适合您所需文本大小的静态高度(始终考虑到文本大小可能会受用户设备偏好或辅助功能设置的影响)。

英文:

One way to do this is to read the height of your text after it is rendered and update your image height.

Try this:

@Composable
fun Test(
) {
    Row(
        modifier = Modifier
            .height(IntrinsicSize.Min)
            .width(200.dp),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        val wantedHeight = remember { mutableStateOf(0) }

        Image(
            painterResource(id = R.drawable.ic_stages),
            contentDescription = null,
            modifier = Modifier
                .padding(end = 8.dp)
                .height(wantedHeight.value.dp)
        )
        Text(
            modifier = Modifier.onGloballyPositioned { coordinates -&gt;
                wantedHeight.value = coordinates.size.height.toDp()
            },
            text = &quot;Yum Yum&quot;,
            color = MaterialTheme.colors.onBackground,
        )
    }
}

Please note, forcing recomposition is prone to creating problems, you might end up in a loop of recompositions or have performance problems.
In this simple example you won't have a problem, but please consider providing a static height for your image that will fit the text size you are after (taking always into account that the text size might be changed by the user device preferences or accessibility settings)

huangapple
  • 本文由 发表于 2023年5月18日 00:22:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76274238.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定