Jetpack Compose: 在 Box 中,基于最大视图的固定宽度和高度

3
我想要一个简单的可组合组件,它可以有两种状态。
1. 如果是状态1 -> 显示一个按钮 2. 如果是状态2 -> 显示一段文本
我面临的问题是按钮比文本要大很多,所以当状态改变时,整个组件的大小也会改变。我希望有一个固定的宽度和高度(基于按钮的大小),这样当显示文本时,它不会影响到宽度和高度。我该如何实现这个?
这是我能做到的最接近的,但还不完美。
@Composable
fun MyComposable(
    modifier: Modifier = Modifier,
    state: State,
    button: @Composable () -> Unit,
) {
    BoxWithConstraints(modifier = modifier) {
        val maxWidth = this.maxWidth.value.toInt()
        val maxHeight = this.maxHeight.value.toInt()
        // Invisibly layout button and measure its size
        var contentSize by remember { mutableStateOf(Pair(0,0)) }
        Layout(content = button, modifier = Modifier.alpha(0f)) { measurables, constraints ->
            val placeable = measurables.first().measure(constraints)
            contentSize = Pair(placeable.width, placeable.height)
            layout(0, 0) {}  // No visible layout
        }

        when (state) {
            State1 -> {
                button()
            }

            State2 -> {
                // enforce text view to have maxWidth and maxHeight
                Text(
                    modifier = modifier.requiredSize(
                        width = contentSize.first.coerceIn(0, maxWidth).dp,
                        height = contentSize.second.coerceIn(0, maxHeight).dp
                    ),
                )
            }
        }
    }
}

如果按钮首先出现并且您需要在之后切换到文本,那么您不需要使用布局。在state1中使用Modifier.onSizeChanged来设置contentSize。 - undefined
2个回答

1
在这种情况下,如果按钮始终比文本更大并且首先出现以获取内容大小,则不需要布局。 您可以通过Modifier.onSizeChanged/onGloballyPositioned/onPlaced获取按钮的大小。
您可以这样做。
@Composable
fun MyComposable(
    modifier: Modifier = Modifier,
    state: LayoutState,
    button: @Composable () -> Unit,
) {

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

        when (state) {
            LayoutState.Button -> {
                Box(
                    modifier = Modifier.onSizeChanged { size: IntSize ->
                        contentSize = with(density) {
                            size.toSize().toDpSize()
                        }
                    }
                ){
                    button()
                }
            }

            LayoutState.Text -> {
                // enforce text view to have maxWidth and maxHeight
                Text(
                    modifier = modifier.size(contentSize),
                    text = "Some Text"
                )
            }
        }
    }
}

额外

当你不知道哪个可组合件可以更大时

如果你希望使用布局,你可以使用两个子组合件而不是空的一个,同时使用这种方法,你不需要额外的重新组合来设置容器的大小以匹配最大的大小,以防止在状态改变时改变大小。

https://dev59.com/scXsa4cB1Zd3GeqPOADp#73357119

使用Layout,您可以获取最大尺寸并根据状态仅放置其中一个。当您不知道哪个可组合对象可能更大时,可以使用此Layout,同样适用于此问题以及任何可组合对象。
enum class LayoutState {
    Button, Text
}

实现测量最大尺寸的布局。在这个示例中,我使用文本的自身尺寸进行测量,但如果有的话,您也可以使用Constraints.Fixed()按钮尺寸来进行测量,而不是使用looseConstraints
@Composable
private fun MyLayout(
    modifier: Modifier = Modifier,
    layoutState: LayoutState,
    button: @Composable (() -> Unit)? = null,
    text: @Composable (() -> Unit)? = null
) {

    val measurePolicy = remember(layoutState) {

        object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {

                val looseConstraints = constraints.copy(
                    minWidth = 0,
                    minHeight = 0
                )

                val buttonPlaceable = measurables.firstOrNull { it.layoutId == "button" }
                    ?.measure(looseConstraints)

                val buttonWidth = buttonPlaceable?.width ?: 0
                val buttonHeight = buttonPlaceable?.height ?: 0

                val textPlaceable = measurables.firstOrNull { it.layoutId == "text" }
                    ?.measure(looseConstraints)

                val textWidth = textPlaceable?.width ?: 0
                val textHeight = textPlaceable?.height ?: 0


                val layoutWidth = buttonWidth.coerceAtLeast(textWidth)
                val layoutHeight = buttonHeight.coerceAtLeast(textHeight)

                return layout(layoutWidth, layoutHeight) {

                    val placeable = if (layoutState == LayoutState.Button) {
                        buttonPlaceable
                    } else {
                        textPlaceable
                    }

                    placeable?.let {
                        // This is for centering Placeable inside Container
                        val xPos = (layoutWidth - placeable.width) / 2
                        val yPos = (layoutHeight - placeable.height) / 2
                        placeable.placeRelative(xPos, yPos)
                    }
                }
            }
        }
    }
    Layout(
        modifier = modifier,
        content = {
            if (button != null) {
                Box(modifier = Modifier.layoutId("button")) {
                    button()
                }
            }

            if (text != null) {
                Box(modifier = Modifier.layoutId("text")) {
                    text()
                }
            }
        },
        measurePolicy = measurePolicy
    )
}

使用方法
@Preview
@Composable
private fun LayoutTest() {

    var layoutState by remember {
        mutableStateOf(LayoutState.Button)
    }

    Column {

        Button(
            onClick = {
                if (layoutState == LayoutState.Button) {
                    layoutState = LayoutState.Text
                } else {
                    layoutState = LayoutState.Button
                }
            }
        ) {
            Text("State: $layoutState")

        }
        MyLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            layoutState = layoutState,
            button = {
                Button(
                    onClick = {

                    }
                ) {
                    Text("Some Button")
                }
            },
            text = {
                Text("Some Text")
            }
        )
        Spacer(modifier = Modifier.height(20.dp))
        MyLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            layoutState = layoutState,
            button = {
                Button(
                    modifier = Modifier.size(200.dp),
                    onClick = {

                    }
                ) {
                    Text("Some Button")
                }
            },
            text = {
                Text("Some Text")
            }
        )

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

        MyLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            layoutState = layoutState,
            button = {
                Button(
                    onClick = {

                    }
                ) {
                    Text("Some Button")
                }
            },
            text = {
                Text(
                    "Some Text",
                    modifier = Modifier.width(160.dp).height(60.dp).background(Color.Red),
                    color = Color.White,
                    textAlign = TextAlign.Center
                )
            }
        )
    }
}

在这两种情况下,我们会根据每个可组合项的大小进行测量,但容器的大小取决于最大的那个。如果您希望重新测量较小的可组合项以包含所有空间,您可以使用SubcomposeLayout

如何调整组件的大小以适应其子组件,并在子组件的大小发生变化时保持不变?(Jetpack Compose)


不客气。在你的情况下,只需使用Modifier.onSizeChanged就足够了,但当你不知道哪一个应该更大时,可以使用layout,而当较小的一个需要重新测量以匹配容器大小时,可以使用SubcomposeLayout。 - undefined

0
我成功地通过简单的更改和使用LocalDensity而不是最大尺寸来修复了它。
val localDensity = LocalDensity.current
var buttontWidth by remember { mutableStateOf(0.dp) }
var buttonHeight by remember { mutableStateOf(0.dp) }

// invisibily layout the content to measure its width and height
Layout(content = button, modifier = Modifier.alpha(0f)) { measurables, constraints ->
    val placeable = measurables.first().measure(constraints)
    contentWidth = with(localDensity) { placeable.width.toDp() }
    contentHeight = with(localDensity) { placeable.height.toDp() }
    layout(0, 0) {}  // No visible layout
}

然后使用buttonHeight/buttonWidth并应用到其他视图上。

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