What is the right way to build a Row of children, ...
# compose
c
What is the right way to build a Row of children, where all children, except the last one, are left-aligned, and the last one is right-aligned?
In my case I have a row which looks like this:
Copy code
[Image] [Text] .....whitespace.... [Checkbox]
s
Put a
Spacer(Modifier.weight(1f))
before the last element, probably. simple smile
c
And my implementation is like:
Copy code
Row(
  modifier = Modifier.fillMaxWidth(),
  horizontalArrangement: Arrangement.SpaceBetween,
) {
  Row {
    Image()
    Text()
  }
  CheckBox()
}
The issue is that when the text is very long, the Checkbox gets pushed to the right
Sorry @sindrenm I’m not sure I follow, could you clarify your meaning?
s
Copy code
Row {
  Text()
  Text()
  Spacer(Modifier.weight(1f)
  Checkbox()
}
That Row in Row should also work, though. IMO, it kind of depends on the semantics of your elements. You might want that solution if your left-aligned elements are related to each other, but not to the last item.
c
The Row inside Row works for the basic case, but the problem manifests when the
Text()
is very wide
s
Not exactly sure if I understand what you say is the problem, but you could try using a
Spacer
instead of the
SpaceBetween
modifier, perhaps.
Copy code
Row(modifier = Modifier.fillMaxWidth()) {
  Row {
    Image()
    Text()
  }

  Spacer(Modifier.weight(1f))

  CheckBox()
}
Can't say for sure if that would help, though. In theory it should result in the same.
Are you trying to make something akin to a typical list item, perchance?
c
yeah it’s nothing fancy… I just don’t want the text to overflow into the Checkbox space
c
Have you tried the Material
ListItem()
? it's perfect for setting up and properly aligning these kinds of rows with content at the start/end of the row
c
@Casey Brooks thanks for the tip, no I haven’t tried that yet. I’ll check it out!
s
I don't exactly know why the check box is pushed out of bounds, but putting a
Modifier.weight(1f)
on the innermost row seems to work, at least:
Copy code
@Preview
@Composable
fun Test() {
    Box(Modifier.fillMaxSize()) {
        Row {
            Row(Modifier.weight(1f)) {
                Icon(Icons.Default.Home, contentDescription = null)
                Text("Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.")
            }

            Checkbox(checked = false, onCheckedChange = {})
        }
    }
}
1
Or, you can roll with Material's
ListItem
, but I believe it's still experimental, and it's also fairly opinionated. But do still check it out. 👍
c
weight
modifier on the Row with Image + Text is your friend here. And also
softWrap = true
and
overflow = TextOverflow.Ellipsis
on the Text component so longer text doesn't get clipped.
s
Can you explain why a
Spacer
in between or Arrangement.SpaceBetween on the topmost one didn't work here? 🙂
I feel like, semantically, they make more sense.
c
Arrangement.SpaceBetween doesn't quite work because depending on the width of the Text, it will keep pushing all other siblings beyond the bounds of the Row, and there will be no space left between siblings. That arrangement should be used if you think there will always be space between siblings, and the siblings have a fixed width, and you want evenly spaced siblings.
The use of Spacer + weight can be done to push the Checkbox to the very right, but again, if the width of Text is variable, it will keep pushing all siblings beyond the bounds of the Row, and because Spacer has the weight modifier, it will take up the remaining space after everything in the Row is measured. So you should add the weight modifier to Text so that they both use the remaining space evenly. But at that point, if Text is going to take up all the remaining space, there really isn't a need for Spacer is there?!
Overall, the weight modifier in a Row or Column is useful to add to whatever child will have variable width/height, and should take the remaining space. For the example above, the Icon and Checkbox are usually a fixed size, and the Text is the variable one.
The float you pass to weight is also useful because that tells the layout how to divvy up the remaining space, in case you want one sibling to take up more space than the other.
You could rewrite the above to have no inner Row, and the visual result should be the same. And arguably the semantics are clearer since here the only sibling with
weight
is Text:
Copy code
Row(Modifier.fillMaxWidth()) {
    Icon(Icons.Default.Home, contentDescription = null)
    Text(
        "Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.",
        Modifier.weight(1f)
    )
    Checkbox(checked = false, onCheckedChange = {})
}
s
Ahh, I see! That makes sense. 👍
Thanks for the explanation and clarification! Much appreciated. simple smile
My thought initially was that since, as you mentioned, the icon and the check box were both fixed sizes, and the text would just vary in size, that the
Spacer(Modifier.weight(1f))
would just vary in size based on the size of the
Text
when it grows/shrinks. But the fact that it can push it out of the bounds of the
Row
makes it much clearer that it's the
Text
(or whatever wraps it) that needs its size constrained instead. 👍
👍 1
With that in mind, I feel like your
Row { Icon Text Checkbox }
makes much more sense, too. 👍
c
Thanks for the discussion and feedback! Prior to reading this I just implemented using
ListItem()
, but I’ll play around to see if a simpler solution suits my needs. Thanks again!
c
As mentioned earlier,
ListItem
is very opinionated towards the layouts in the Material spec. So very useful for quickly getting a nice list item done, but if you want to customize it, I’d suggest creating a custom one yourself versus trying to fight against the templates.
If you peek at the source code of the
ListItem
templates, it is mostly layout code that is opinionated with certain defaults and hard-coding, so it’s not anything that can’t easily be copied and modified to create your own templates