获取元素的高度 Jetpack Compose

Ren*_*ele 50 android android-jetpack-compose

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

use*_*620 69

如果您想在合成后获取按钮的高度,那么您可以使用:onGloballyPositionedModifier

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

使用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")
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 注意:onGloballyPositioned 返回的大小以 px 为单位,因此我在使用之前将其转换为 dp(例如设置填充、高度等)。 (10认同)
  • val heightInDp = with(LocalDensity.current) { heightInPx.toDp() } (6认同)
  • 如果您正在执行一些 UI 操作(例如调整大小或绘图),您会遇到屏幕上的跳跃或闪烁。因为使用“Modifier.onSizeChanged”或“Modifier.onGloballyPositioned”需要您具有“MutableState”,当您在这些函数内设置时,它会触发重组,这在某些情况下可能会很明显。如果是这种情况,请使用“BoxWithConstraints”,并且在某些情况下最大高度约束不等于实际高度,那么您需要使用 SubcomposeLayout (2认同)

F.M*_*sir 12

完整的解决方案如下:

@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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Thr*_*ian 9

如果您不小心,就像 OP 问题中的那样,当一个可组合项的大小影响另一个可组合项时,使用Modifier.onSizeChanged{}or可能会导致无限重组。Modifier.globallyPositioned{}

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)
    )
}
Run Code Online (Sandbox Code Playgroud)

下次重组时,文本背景将从覆盖其边界的初始背景移动到 200.dp 大小。如果您正在执行将任何 UI 绘图从一个维度更改为另一个维度的操作,则它可能看起来像是闪烁或故障。

在不重新组合的情况下获取元素高度的第一个替代方法是使用BoxWithConstraints

BoxWithConstraints'BoxScope包含在 Int 中的maxHeightdp 和constraints.maxHeight 中。

但是,在某些情况下,BoxWithConstraints 返回的约束不是精确的大小,例如使用 Modifier.fillMaxHeight、没有任何大小修饰符或具有垂直滚动的父项返回不正确的值

您可以查看有关从 BoxWithConstraints 返回的尺寸的答案,约束部分显示了使用 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 }
Run Code Online (Sandbox Code Playgroud)

用法

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
)
Run Code Online (Sandbox Code Playgroud)

或者

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