alessandro gaboardi
alessandro gaboardi

Reputation: 959

How to achieve this layout in Jetpack Compose

I'm trying to use the new Jetpack Compose UI framework, but I'm running into an issue. I'd like to achieve this layout, which in xml is pretty easy to achieve:

Correct layout

But I can't figure out how to make the vertical divider take up the available vertical space, without specifying a fixed height. This code that I've tried doesn't seem to work:

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

I keep getting this result:

enter image description here

I also tried with ConstraintLayout, but it still didn't work

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

But nothing seems to work. Is this a bug, a missing feature or am I just missing something?

EDIT: Apparently the real problem is that the divider doesn't know how to measure when the Surface doesn't have a fixed height, setting height equal to some number solves the issue, but then the view doesn't adapt to the content height anymore, so this can't be the solution

Upvotes: 11

Views: 13108

Answers (5)

Gabriele Mariotti
Gabriele Mariotti

Reputation: 363439

You can apply:

  • the modifier .height(IntrinsicSize.Max) to the Row
  • the modifiers .width(1.dp).fillMaxHeight() to the Spacer

You can read more about the Intrinsic measurements here.

Something like:

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

enter image description here

Upvotes: 11

Simsim
Simsim

Reputation: 321

I think Row layout is enough.

@Preview(showBackground = true, heightDp = 100)
@Composable
fun ListItem(item: PlateUI.Plate = PlateUI.Plate()) {
    Card(
        shape = RoundedCornerShape(8.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxSize(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                modifier = Modifier.padding(8.dp),
                text = "Code\n${item.code}",
                textAlign = TextAlign.Center
            )
            Box(
                Modifier
                    .fillMaxHeight()
                    .width(1.dp)
                    .background(color = MaterialTheme.colors.onSurface.copy(0.12f))
            )
            Text(
                modifier = Modifier
                    .weight(1f)
                    .padding(8.dp),
                text = item.name,
                textAlign = TextAlign.Center
            )
        }
    }
}

List Item Preview

Upvotes: 1

Aaron Austin Quaday
Aaron Austin Quaday

Reputation: 26

There are plenty of solutions here, but I thought I could demonstrate the ConstraintLayout approach and add a helpful usage of the IntrinsicSize enum that solves one of the issues (needing an adaptive height for the composable). Interestingly, either IntrinsicSize.Max or IntrinsicSize.Min will yield the desired behavior.

I used most of your code. The key differences are:

  • declares a guideline (my value passed in for the fraction does not produce the exact result you were looking for, but can be adjusted easily (use a fraction slightly smaller than .2) This can be useful if you expect wrapContent to alter your left Text to vary the location of a spacer, but would prefer a consistent spacer location across a list of these items.
  • others have mentioned, spacer modifier should include .fillMaxHeight()
  • define the height of the surface wrapper to be .height(IntrinsicSize.Min) docs ref here: https://developer.android.com/jetpack/compose/layout#intrinsic-measurements
  • divider start is constrained to the guideline
  • had to change the Spacer modifier to access the width, instead of 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
            )
        }
    }
}

Upvotes: 0

GGDev
GGDev

Reputation: 600

You can set Intrinsic.Max for the preferredHeight of the Row, then set the Spacer to fill max height. You can read more on Intrinsics in this codelab section.

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

Upvotes: 4

Lukas1
Lukas1

Reputation: 582

I've solved it using constraint layout:

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

The key part is using that modifier height = Dimension.fillToConstraints

Upvotes: 1

Related Questions