英文:
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
之前的版本升级到该版本或更高版本时发生的变化。
例如:
由于这是一个简单的任务,并且那次更改很久以前就已经进行了,而且没有关于它的投诉(我找不到),我猜测我当时做的事情是错误的,只是起作用,现在我可能漏掉了其他一些东西。
非常感谢任何帮助!谢谢!
英文:
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.
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:
-
> Asking for intrinsics measurements doesn't measure the children twice.
> Children are queried for their intrinsic measurements before they'remeasured and then, based on that information the parent calculates the
constraints to measure its children with.
Link to Android documentation -
> 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 -
Modifier.onSizeChanged
orModifier.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 useSubcomposeLayout
. Link to Android documentation -
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, ) }
-
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) } } }
-
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 = "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://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 = "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,
)
}
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)
}
}
}
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 = "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,
)
}
}
}
答案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 ->
wantedHeight.value = coordinates.size.height.toDp()
},
text = "Yum Yum",
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论