我正在构建一种可组合的类似:

@Composable
fun BottomSectionWrapper(
    content: @Composable () -> Unit,
    bottomSection: @Composable () -> Unit,
)

我现在想知道参数bottomSection是否为空,以便我可以相应地调整我的布局.这个是可能的吗?


我不会再给"空"下更多的定义;我会接受任何技术上可能的东西:

  • 空的lambda{}
  • 不发出可组合元素的lambda:{if(False)Text("Test")}
  • 零大小的lambda:{Box(Modifier.Size(0.dp))}

让我解释一下我的典型用例: 我正在使用这个包装器将按钮连接到屏幕的下端. 如果有一个按钮,我想添加一个淡入淡出.


我都试了些什么?

我try 使lambda可为空--但没有编译

BottomSectionWrapper(
    content = { /* ... */ },
    bottomSection = if(showDeleteButton){
        Button { Text("Delete") }
    } else null
)

另外:直觉上,我会这样写(这不会被简单的空判断检测到):

BottomSectionWrapper(
    content = { /* ... */ },
    bottomSection = {
        if (showDeleteButton) {
            Button { Text("Delete") }
        }
    }
)

推荐答案

如果希望具有可为空的内容,则需要将声明设置为

@Composable
fun BottomSectionWrapper(
    modifier: Modifier = Modifier,
    content: @Composable (() -> Unit)? = null,
    bottomSection: @Composable (() -> Unit)? = null,
) {
    Column(modifier = modifier) {
        content?.invoke()
        bottomSection?.invoke()
    }
}

您可以像在此演示中一样使用它

@Preview
@Composable
private fun BottomSectionWrapperTest() {

    Column {
        var isSet by remember {
            mutableStateOf(false)
        }

        Button(onClick = { isSet = isSet.not() }) {
            Text("isSet: $isSet")
        }
        

        val bottomSection: (@Composable () -> Unit)? = if (isSet) {
            {
                Box(modifier = Modifier.size(100.dp).background(Color.Green)) {
                    Text("Bottom Section")
                }
            }

        } else null


        BottomSectionWrapper(
            modifier = Modifier.border(2.dp, Color.Red),
            content = {
                Text("Content Section")
            },
            bottomSection = bottomSection
        )
    }
}

如果您希望检测Composable是空的,还是有0.dp大小或任何大小,您可以使用Layout,具有不同的方法、尺寸设置和放置选项.

第二个Modifier.layoutId用于默认的合成体,如TextFieldTab

@Composable
fun BottomSectionWrapperLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit = {},
    bottomSection: @Composable () -> Unit = {},
) {

    Layout(
        modifier = modifier,
        contents = listOf(content, bottomSection)
    ) { measurables: List<List<Measurable>>, constraints: Constraints ->

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

        val contentPlaceable: Placeable? =
            measurables.first().firstOrNull()?.measure(looseConstraints)

        val bottomSectionPlaceable: Placeable? =
            measurables.last().firstOrNull()?.measure(looseConstraints)

        println("content: $contentPlaceable, bottomSectionPlaceable: $bottomSectionPlaceable")

        // you can set width and height as required, i make it as a Column
        // this width and height determines whether it's a Column, Row or a Box

        val contentWidth = contentPlaceable?.width ?: 0
        val contentHeight = contentPlaceable?.height ?: 0

        val bottomSectionWidth = bottomSectionPlaceable?.width ?: 0
        val bottomSectionHeight = bottomSectionPlaceable?.height ?: 0

        val totalWidth = if (constraints.hasFixedWidth && constraints.hasBoundedWidth)
            constraints.maxWidth else contentWidth.coerceAtLeast(bottomSectionWidth)
            .coerceIn(constraints.minWidth, constraints.maxWidth)

        val totalHeight = if (constraints.hasFixedHeight && constraints.hasBoundedHeight)
            constraints.maxHeight else (contentHeight + bottomSectionHeight)
            .coerceIn(constraints.minHeight, constraints.maxHeight)

        layout(totalWidth, totalHeight) {
            // You can place them according to your logic here
            contentPlaceable?.placeRelative(0, 0)
            bottomSectionPlaceable?.placeRelative(0, contentHeight)
        }
    }
}

对于这Layout,您可以通过判断Placeable是否为空(这意味着传递空的lambda)来检测它们中是否有任何一个为空,如果不为空,则可以判断它的大小.

@Preview
@Composable
private fun LayoutTest() {
    Column(modifier = Modifier.fillMaxSize()) {
        BottomSectionWrapperLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            content = {
                Text("Content Section")
            },
            bottomSection = {
                Text("Bottom Section")
            }
        )

        Spacer(Modifier.height(20.dp))
        BottomSectionWrapperLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            bottomSection = {
                Text("Bottom Section")
            }
        )

        Spacer(Modifier.height(20.dp))
        BottomSectionWrapperLayout(
            modifier = Modifier.border(2.dp, Color.Red),
            content = {
                Text("Content Section")
            }
        )
   
    }
}

如果您查看println,您可以看到在每种情况下,您将能够看到哪些是空的,哪些不是.此外,您还可以获得可放置的尺寸.

另一个选项是创建此布局,就像Textfield在第495行中所做的那样.

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=495?q=TextField&ss=androidx%2Fplatform%2Fframeworks%2Fsupport

但是使用这种方法,您将无法判断是否传递了空lambda,因为当它不为空时,它被放在一个Box中.然而,这种方法经常被使用,因为您可以通过ID而不是判断列表来判断和比较内容.

@Composable
fun BottomSectionWrapperLayout2(
    modifier: Modifier = Modifier,
    content: @Composable (() -> Unit)? = null,
    bottomSection: @Composable (() -> Unit)? = null,
) {

    Layout(
        modifier = modifier,
        content = {

            if (content != null) {
                Box(
                    modifier = Modifier.layoutId("content")
                ) {
                    content()
                }
            }

            if (bottomSection != null) {
                Box(
                    modifier = Modifier.layoutId("bottomSection")
                ) {
                    bottomSection()
                }
            }
        }
    ) { measurables: List<Measurable>, constraints: Constraints ->

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

        val contentPlaceable: Placeable? =
            measurables.find { it.layoutId == "content" }?.measure(looseConstraints)
        val bottomSectionPlaceable =
            measurables.find { it.layoutId == "bottomSection" }?.measure(looseConstraints)

        // you can set width and height as required, i make it as a Column
        // this width and height determines whether it's a Column, Row or a Box

        val contentWidth = contentPlaceable?.width ?: 0
        val bottomSectionWidth = bottomSectionPlaceable?.width ?: 0

        val contentHeight = contentPlaceable?.height ?: 0
        val bottomSectionHeight = bottomSectionPlaceable?.height ?: 0

        val totalWidth = if (constraints.hasFixedWidth && constraints.hasBoundedWidth)
            constraints.maxWidth else contentWidth.coerceAtLeast(bottomSectionWidth)
            .coerceIn(constraints.minWidth, constraints.maxWidth)

        val totalHeight = if (constraints.hasFixedHeight && constraints.hasBoundedHeight)
            constraints.maxHeight else (contentHeight + bottomSectionHeight)
            .coerceIn(constraints.minHeight, constraints.maxHeight)


        layout(totalWidth, totalHeight) {
            contentPlaceable?.placeRelative(0, 0)
            bottomSectionPlaceable?.placeRelative(0, contentHeight)
        }
    }
}

用法与其他布局相同.

Android相关问答推荐

在Android中点击滑动时触发的手势

如何在Android Jetpack Compose中找到我的手机屏幕一行有多少个单词

数据绑定在Android中等待填充值时显示未填充的值

为什么可以';我不能直接在RecyclerView.ViewHolder中访问视图ID吗?

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

如何在 Jetpack Compose LazyColumn 中将项目分组在一起,例如卡片

为什么第二个代码可以安全地在 map 中进行网络调用,因为它已被缓存?

尽管我在onCreate()期间已经初始化,但仍出现未初始化的late init变量错误

发布 MAUI 应用程序时出现遇到重复程序集错误

Android Studio XML 文本在 ConstraintLayout 中不居中

组成不重叠的元素

LazyColumn 项目,隐式接收器无法在此上下文中调用单元

如何在 Android 应用程序未激活/未聚焦时显示视图?

错误:构建 react-native 应用程序时包 com.facebook.react.bridge 不存在

为 AlertDialog 的消息文本设置自定义字体

重命名列失败的房间自动迁移(NOT NULL 约束失败,生成错误的迁移类)

Android 12 通过包管理器中断 APK 安装?

单击登录按钮后从应用程序中退出

如何将私有 mutableStateOf 分配给 Android Jetpack 中的 State 变量?

即使我在单选按钮上明确设置了选中状态,RecyclerView 中的单选按钮也会随机取消选中