如何在 Jetpack Compose 中实现这种布局

How to achieve this layout in Jetpack Compose

我正在尝试使用新的 Jetpack Compose UI 框架,但我 运行 遇到了问题。我想实现这种布局,在 xml 中很容易实现:

但我不知道如何在不指定固定高度的情况下让垂直分隔线占据可用的垂直空间 space。我试过的这段代码似乎不起作用:

@Composable
fun ListItem(item: PlateUI.Plate) {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(8.dp),
        elevation = 2.dp
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Column(
                modifier = Modifier
                    .padding(8.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = "Code")
                Text(text = item.code)
            }
            Spacer(
                modifier = Modifier
                    .preferredWidth(1.dp)
                    .background(color = MaterialTheme.colors.onSurface.copy(0.12f))
            )
            Spacer(modifier = Modifier.weight(1f))
            Text(
                modifier = Modifier
                    .padding(horizontal = 8.dp, vertical = 34.dp),
                text = item.name
            )
            Spacer(modifier = Modifier.weight(1f))
        }
    }
}

我一直得到这个结果:

我也试过ConstraintLayout,还是不行

@Composable
fun ListItem(item: PlateUI.Plate) {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(8.dp),
        elevation = 2.dp
    ) {
        ConstraintLayout(
            modifier = Modifier.fillMaxWidth(),
        ) {
            val(column, divider, text) = createRefs()
            Column(
                modifier = Modifier
                    .padding(8.dp)
                    .constrainAs(column){
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                    },
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = "Code")
                Text(text = item.code)
            }
            Spacer(
                modifier = Modifier
                    .preferredWidth(1.dp)
                    .background(color = MaterialTheme.colors.onSurface.copy(0.12f))
                    .constrainAs(divider){
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(column.end)
                    }
            )
            Text(
                modifier = Modifier
                    .padding(horizontal = 8.dp, vertical = 34.dp)
                    .constrainAs(text){
                        start.linkTo(divider.end)
                        end.linkTo(parent.end)
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                    },
                text = item.name
            )
        }
    }
}

但似乎没有任何效果。这是错误、缺失的功能还是我只是遗漏了什么?

编辑:显然,真正的问题是当 Surface 没有固定高度时,分隔线不知道如何测量,将高度设置为某个数字可以解决问题,但视图却没有不再适应内容高度,所以这不是解决方案

1.0.0-beta02可以申请:

  • 修饰符 .height(IntrinsicSize.Max)Row
  • 修饰符 .width(1.dp).fillMaxHeight()Spacer

您可以阅读更多关于内部测量值 here

类似于:

Row(
    modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
    verticalAlignment = Alignment.CenterVertically
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        ....
    ) {
        Text(text = "....")
    }
    Spacer(
        modifier = Modifier
            .width(1.dp)
            .fillMaxHeight()
            .background(color = MaterialTheme.colors.onSurface.copy(0.12f))
    )
    Text(...)
}

我已经使用约束布局解决了它:

Box(modifier = Modifier.padding(Dp(50f))) {
    ConstraintLayout(
        modifier = Modifier
            .border(width = Dp(1f), color = Color.Black)
            .fillMaxWidth()
    ) {
        val (left, divider, right) = createRefs()
        Column(
            modifier = Modifier
                .padding(horizontal = Dp(20f))
                .constrainAs(left) {
                    width = Dimension.wrapContent
                    start.linkTo(parent.start)
                    top.linkTo(parent.top)
                    end.linkTo(divider.start)
                    bottom.linkTo(parent.bottom)
                }
        ) {
            Text(text = "Code")
            Text(text = "A12")
        }
        Box(
            modifier = Modifier
                .width(Dp(1f))
                .background(Color.Black)
                .constrainAs(divider) {
                    width = Dimension.wrapContent
                    height = Dimension.fillToConstraints
                    start.linkTo(left.end)
                    top.linkTo(parent.top)
                    end.linkTo(right.start)
                    bottom.linkTo(parent.bottom)
                }
        )
        Box(
            modifier = Modifier
                .constrainAs(right) {
                    width = Dimension.fillToConstraints
                    start.linkTo(divider.end)
                    top.linkTo(parent.top)
                    end.linkTo(parent.end)
                    bottom.linkTo(parent.bottom)
                }
        ) {
            Text(
                text = "Test",
                modifier = Modifier
                    .padding(vertical = Dp(100f))
                    .align(Alignment.Center)
            )
        }
    }
}

关键部分是使用那个修饰符height = Dimension.fillToConstraints

您可以为 RowpreferredHeight 设置 Intrinsic.Max,然后设置 Spacer 以填充最大高度。您可以在 this codelab section.

中阅读有关 Intrinsic 的更多信息
@Composable
fun ListItem() {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(8.dp),
        elevation = 2.dp
    ) {
        Row(
            modifier = Modifier.fillMaxWidth().preferredHeight(IntrinsicSize.Max),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Column(
                modifier = Modifier
                    .padding(8.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = "Code")
                Text(text = "2456")
            }
            Spacer(
                modifier = Modifier
                    .preferredWidth(1.dp)
                    .fillMaxHeight()
                    .background(color = Color.Black.copy(0.12f))
            )
            Spacer(modifier = Modifier.weight(1f))
            Text(
                modifier = Modifier
                    .padding(horizontal = 8.dp, vertical = 34.dp),
                text = "Some name"
            )
            Spacer(modifier = Modifier.weight(1f))
        }
    }
}

这里有很多解决方案,但我想我可以演示 ConstraintLayout 方法并添加一个有用的 IntrinsicSize 枚举用法来解决其中一个问题(需要可组合项的自适应高度)。有趣的是,IntrinsicSize.MaxIntrinsicSize.Min 都会产生所需的行为。

我使用了你的大部分代码。主要区别是:

  • 声明一个准则(我为分数传递的值不会产生您正在寻找的确切结果,但可以轻松调整(使用略小于 .2 的分数)如果您期望 wrapContent,这将很有用更改左侧文本以改变分隔符的位置,但希望在这些项目的列表中使用一致的分隔符位置。
  • 其他人已经提到,间隔修饰符应该包括.fillMaxHeight()
  • 将表面包装的高度定义为 .height(IntrinsicSize.Min) 此处的文档参考:https://developer.android.com/jetpack/compose/layout#intrinsic-measurements
  • 分频器开始受制于准则
  • 必须更改 Spacer 修饰符才能访问 width,而不是 preferredWidth
@Composable
fun ListItem(item: Plate) {
    Surface(
        modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
        shape = RoundedCornerShape(8.dp),
        elevation = 2.dp
    ) {
        ConstraintLayout(
            modifier = Modifier.fillMaxWidth(),
        ) {
            val guideline = createGuidelineFromStart(0.2f)

            val(column, divider, text) = createRefs()

            Column(
                modifier = Modifier
                    .padding(8.dp)
                    .constrainAs(column){
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(guideline)
                    },
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = "Code")
                Text(text = item.code)
            }
            Spacer(
                modifier = Modifier
                    .constrainAs(divider){
                        top.linkTo(column.top)
                        bottom.linkTo(column.bottom)
                        start.linkTo(guideline)
                    }
                    .width(1.dp)
                    .fillMaxHeight()
                    .background(color = MaterialTheme.colors.onSurface.copy(0.12f))
            )
            Text(
                modifier = Modifier
                    .padding(horizontal = 8.dp, vertical = 34.dp)
                    .constrainAs(text){
                        start.linkTo(divider.end)
                        end.linkTo(parent.end)
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                    },
                text = item.name
            )
        }
    }
}