我想有一个简单的可组合组件,可以有2个状态

  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
                    ),
                )
            }
        }
    }
}

推荐答案

在这种情况下,如果Button始终大于Text,则不需要Layout,并且它首先出现在获取Content Size和的位置. 你可以从Modifier.onSizeChanged/onGloballyPositioned/onPlaced号到Button号.

你可以这样做

@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"
                )
            }
        }
    }
}

Extra

当你不知道哪种合成物更大的时候

如果你想使用一个布局,你可以拥有两个子组件而不是空的,而且使用这种方法,你不需要额外的重新组合来获得容器的设置大小来匹配最大大小,这样它就不会在状态改变时改变大小.

https://stackoverflow.com/a/73357119/5457853

如果设置为Layout,您可以获得最大尺寸,并根据状态只放置其中一个尺寸.当你不知道哪个Composable可以更大的时候,你可以使用这个Layout,这也适用于这个问题,就像它适用于任何Composable一样.

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
                )
            }
        )
    }
}

在这两种情况下,我们都用它们自己的大小来衡量每个可合成material ,但容器的大小是最大的.如果您想重新测量包含所有空间的较小的一个,您可以使用SubcomposeLayout

How to adjust size of component to it's child and remain unchanged when it's child size will change? (Jetpack Compose)

Android相关问答推荐

Jetpack Compose中的导航找不到NavHost类的名称为:startDestination";的参数

合成-删除图像的部分背景

Android-LVL库始终返回NOT_SUBLISTED

如何在喷气背包中绕过集装箱

SmsMessage如何在Kotlin库中工作?

Visual Studio 2022,毛伊岛,Android Emulator:无法更改内存量

使用lazyColumn迁移paging3的旧代码

如何使用react native下载android中/data/data/[应用程序包名称文件夹]/files中的文件

是否可以在 Android 应用程序的 Wifi 设置中为 DNS 服务器设置自定义 IP?

如何在这段代码android jetpack compose中实现全屏滚动

如何仅使用您的 Android 应用程序定位平板电脑?

在 kotlin 中动态添加 GridView

Android 设备断开连接后发送的 BLE 蓝牙数据

React Native Android 应用程序在调试模式下运行良好,但当我们发布 apk 时,它会生成旧版本的应用程序

服务似乎在启动时忽略传递的变量

单击 Jetpack Compose(单选)时,我无法为列表中的单个文本着色

无法通过 retrofit 解析对 kotlin 数据类的 xml 响应

前台服务通知需要几秒钟才能显示

为什么官方文档用大写字母表示val变量?

复用 RecyclerView 适配器,避免不必要的 API 调用