获取Jetpack Compose元素的高度

44
我想获取Jetpack Compose中我的按钮(或其他元素)的高度。你知道如何获取吗?

6
使用Modifier.onSizeChanged() - CommonsWare
你需要使用一个组件的大小来影响另一个组件的大小吗?还是只是获取高度? - Gabriele Mariotti
@GabrieleMariotti,影响另一个组件的大小。CommonsWare,谢谢! - Renattele Renattele
2
在这种情况下使用布局。 - Gabriele Mariotti
@CommonsWare的最佳最简单答案 - Marine Droit
5个回答

60

如果您想在组合后获取按钮的高度,则可以使用:OnGloballyPositionedModifier

它返回一个LayoutCoordinates对象,其中包含您的按钮的大小。

使用onGloballyPositioned修饰符的示例:

@Composable
fun OnGloballyPositionedExample() {
    // Get local density from composable
    val localDensity = LocalDensity.current
    
    // Create element height in pixel state
    var columnHeightPx by remember {
        mutableStateOf(0f)
    }

    // Create element height in dp state
    var columnHeightDp by remember {
        mutableStateOf(0.dp)
    }

    Column(
        modifier = Modifier
            .onGloballyPositioned { coordinates ->
                // Set column height using the LayoutCoordinates
                columnHeightPx = coordinates.size.height.toFloat()
                columnHeightDp = with(localDensity) { coordinates.size.height.toDp() }
            }
    ) {
        Text(text = "Column height in pixel: $columnHeightPx")
        Text(text = "Column height in dp: $columnHeightDp")
    }
}

10
注意:onGloballyPositioned返回的是以像素(px)为单位的尺寸,因此在使用前我将其转换为设备独立像素(dp),例如设置填充、高度等。 - Renattele Renattele
如何转换为dp? - Hussien Fahmy
6
val heightInDp = with(LocalDensity.current) { heightInPx.toDp() }翻译: 通过 LocalDensity.current, 将高度从像素单位转换为 dp 单位,得到的结果为 heightInDp。 - user3872620
1
填充和预览不太好。 - Psijic
1
如果您正在执行一些UI操作,例如调整大小或绘制,则会在屏幕上出现跳动或闪烁。因为使用Modifier.onSizeChangedModifier.onGloballyPositioned需要您拥有MutableState,当您在这些函数内设置时会触发重新组合,这在某些情况下可能会引起注意。如果是这种情况,请使用BoxWithConstraints,并且在某些条件下,最大高度约束不等于实际高度,则需要使用SubcomposeLayout。 - Thracian

11

一个完整的解决方案如下:

@Composable
fun GetHeightCompose() {
    // get local density from composable
    val localDensity = LocalDensity.current
    var heightIs by remember {
        mutableStateOf(0.dp)
    }
    Box(modifier = Modifier.fillMaxSize()) {
        // Important column should not be inside a Surface in order to be measured correctly
        Column(
            Modifier
                .onGloballyPositioned { coordinates ->
                    heightIs = with(localDensity) { coordinates.size.height.toDp() }
                }) {
            Text(text = "If you want to know the height of this column with text and button in Dp it is: $heightIs")
            Button(onClick = { /*TODO*/ }) {
                Text(text = "Random Button")
            }
        }
    }
}

8
使用Modifier.onSizeChanged{}Modifier.globallyPositioned{}可能会导致无限重新组合,如果你不小心的话,就像在OPs问题中一个Composable的大小影响另一个Composable时一样。

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

使用MutableState中的onSizeChanged大小值来更新布局会导致在接下来的帧中读取新的大小值并重新组合布局,从而导致一帧延迟。您可以使用onSizeChanged来影响绘图操作。使用Layout或SubcomposeLayout使一个组件的大小影响另一个组件的大小。即使用户可以注意到帧之间的变化,如果绘制出现问题也不好看。例如:
Column {

    var sizeInDp by remember { mutableStateOf(DpSize.Zero) }
    val density = LocalDensity.current

    Box(modifier = Modifier
        .onSizeChanged {
            sizeInDp = density.run {
                DpSize(
                    it.width.toDp(),
                    it.height.toDp()
                )
            }
        }

        .size(200.dp)
        .background(Color.Red))

    Text(
        "Hello World",
        modifier = Modifier
            .background(Color.White)
            .size(sizeInDp)
    )
}

文本背景从初始背景覆盖其边界到下一次重新组合时的200.dp大小移动。如果您正在执行任何将UI绘制从一个维度更改为另一个维度的操作,它可能看起来像是闪光或故障。
获取元素高度而不重新组合的第一个替代方法是使用BoxWithConstraints。
BoxWithConstraints的BoxScope包含dp中的maxHeight和Int中的constraints.maxHeight。
然而,BoxWithConstraints在某些条件下返回约束而不是精确大小,例如使用Modifier.fillMaxHeight、没有任何大小修饰符或父级具有垂直滚动会返回不正确的值。
您可以查看关于从BoxWithConstraints返回的尺寸的this answer,Constraints部分显示了使用BoxWithConstraints时会得到什么。
verticalScroll返回高度的Constraints.Infinity。
获取精确大小的可靠方法是使用SubcomposeLayout。 如何在不重新组合的情况下获取精确大小?
**
 * SubcomposeLayout that [SubcomposeMeasureScope.subcompose]s [mainContent]
 * and gets total size of [mainContent] and passes this size to [dependentContent].
 * This layout passes exact size of content unlike
 * BoxWithConstraints which returns [Constraints] that doesn't match Composable dimensions under
 * some circumstances
 *
 * @param placeMainContent when set to true places main content. Set this flag to false
 * when dimensions of content is required for inside [mainContent]. Just measure it then pass
 * its dimensions to any child composable
 *
 * @param mainContent Composable is used for calculating size and pass it
 * to Composables that depend on it
 *
 * @param dependentContent Composable requires dimensions of [mainContent] to set its size.
 * One example for this is overlay over Composable that should match [mainContent] size.
 *
 */
@Composable
fun DimensionSubcomposeLayout(
    modifier: Modifier = Modifier,
    placeMainContent: Boolean = true,
    mainContent: @Composable () -> Unit,
    dependentContent: @Composable (Size) -> Unit
) {
    SubcomposeLayout(
        modifier = modifier
    ) { constraints: Constraints ->

        // Subcompose(compose only a section) main content and get Placeable
        val mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent)
            .map {
                it.measure(constraints.copy(minWidth = 0, minHeight = 0))
            }

        // Get max width and height of main component
        var maxWidth = 0
        var maxHeight = 0

        mainPlaceables.forEach { placeable: Placeable ->
            maxWidth += placeable.width
            maxHeight = placeable.height
        }

        val dependentPlaceables: List<Placeable> = subcompose(SlotsEnum.Dependent) {
            dependentContent(Size(maxWidth.toFloat(), maxHeight.toFloat()))
        }
            .map { measurable: Measurable ->
                measurable.measure(constraints)
            }


        layout(maxWidth, maxHeight) {

            if (placeMainContent) {
                mainPlaceables.forEach { placeable: Placeable ->
                    placeable.placeRelative(0, 0)
                }
            }

            dependentPlaceables.forEach { placeable: Placeable ->
                placeable.placeRelative(0, 0)
            }
        }
    }
}

enum class SlotsEnum { Main, Dependent }

使用方法

val content = @Composable {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.Red)
    )
}

val density = LocalDensity.current

DimensionSubcomposeLayout(
    mainContent = { content() },
    dependentContent = { size: Size ->
        content()
        val dpSize = density.run {size.toDpSize() }
        Box(Modifier.size(dpSize).border(3.dp, Color.Green))
    },
    placeMainContent = false
)

或者

DimensionSubcomposeLayout(
    mainContent = { content() },
    dependentContent = { size: Size ->
        val dpSize = density.run {size.toDpSize() }
        Box(Modifier.size(dpSize).border(3.dp, Color.Green))
    }
)

我使用了“Layout”组合函数来实现这个功能,使用一个传递给layout = {content(size)}的变量,然后在Layout的测量策略范围内计算并重新分配size变量的值。虽然它能正常工作,但我仍然有些怀疑,你觉得呢?提前感谢。 - undefined
工作得很顺利!谢谢 - undefined

1
您也可以像下面这样使用BoxWithConstraints
Button(onClick = {}){

   BoxWithConstraints{
     val height = maxHeight

   }
}

但我不确定它是否适用于您的特定用例。

0

我成功地实现了一个解决方法,可以通过使从中读取的组合现在成为无状态的来减少(而不是完全消除)使用Modifier.onSizeChanged时的重新组合次数。虽然这不是最好的方法,但它能够完成工作。

@Composable
fun TextToMeasure(
    currentHeight: Dp,
    onHeightChanged: (Dp) -> Unit,
    modifier: Modifier = Modifier,
) {
    val density = LocalDensity.current

    Text(
        text = "Hello Android",
        modifier = modifier
            .onSizeChanged { size ->
                val newHeight = with(density) { size.height.toDp }
                
                if (newHeight != currentHeight) {
                    onHeightChanged(newHeight)
                }
            },
    )
}

@Composable
fun MainScreen() {
    val height = remember { mutableStateOf(0.dp) }

    TextToMeasure(
        currentHeight = height.value,
        onHeightChanged = { height.value = it },
    )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接