Does anyone have a good idea to how one would alig...
# compose
s
Does anyone have a good idea to how one would align the two second columns in this case? As far as I can tell, there are several options, but they all have some downsides. 1. I could remember the widest time/duration column width and use that as a minimum width for all of them. This, however, would cause incorrectness on the first frame, which is not ideal. 2. I could set a fixed actual width for the time/duration column. However, I would need to find some magic number for this, and it would also not work great with different font scales. 3. I could run a custom layout, but that seems a little overkill in this scenario. However, it might be what we're going for.| Anyone deal with a similar issue have a different solution?
e
custom layout wouldn't be too bad but this looks like something that should be easy in https://developer.android.com/develop/ui/compose/layouts/constraintlayout
e
Hey there, I’m assuming you’re using a LazyColumn or some other form of Lazy list. If so, you could have each Row with a
SpaceBetween
arrangement and then wrap each column in a
Box
with a
weight
modifier. This would get you your desired alignment, regardless of font and text size on the first column. Very sloppy code to give you a general idea of what I mean, as well as a screenshot of the result. Hope this helps!
Copy code
LazyColumn {
        val items = listOf(
            Triple<String, String, String>("test", "testwsets", "test"),
            Triple<String, String, String>("t", "testwsets", "test"),
            Triple<String, String, String>("123123123", "testwsets", "test")
        )
        items(items) { item ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(modifier = Modifier.weight(1f)) {
                    Column {
                        Text(
                            text = item.first
                        )
                        Text(
                            text = item.first
                        )
                    }
                }

                Box(
                    modifier = Modifier.weight(2f)
                ) {
                    Column() {
                        Text(
                            text = item.second
                        )
                        Text(
                            text = item.second
                        )
                        Text(
                            text = item.second
                        )
                    }
                }

                Box(
                    modifier = Modifier.weight(1f),
                    contentAlignment = Alignment.CenterEnd
                ) {
                    Button(
                        onClick = { /* Booking logic */ },
                        modifier = Modifier.width(100.dp)
                    ) {
                        Text("Book")
                    }
                }
            }
        }
    }
e
it looks like a card which precludes lazy layouts inside of it
👍 1
e
I don’t see a reason this wouldn’t work outside of a Lazy layout though, should be easy to adapt to regular Rows inside a Card. It’s simply adding Boxes with weights inside the Row which is likely already being used.
w
Weights/static widths wouldn't help with the text scaling problem. I say use TextMeasurer, pre-measure the max width of the time column (either worst case ems or your actual data), and use that as the fixed width. This should be possible to do synchronously on the first frame, so you don't have the same problem like tracking max width.
s
I'd go with textMeasurer and measuring all the options and taking the max width as Winson suggests above if you are familiar with those APIs. Another, defeinitely less performant approach if the list is not a very big one and you are ok with measuring some extra layouts that you will never place, would be doing something like this: Each entry takes in the entire list of all of the entries that may show there. You do:
Copy code
Row {
 Box() {
  TimeAndDurationColumn("5:00 PM", "45 min")
  for (otherEntry in allEntries) {
    TimeAndDurationColumn(otherEntry, Modifier.withoutPlacement())
  }
 }
 Column {
  IndoorLifting(); SATSetc...
 }
}
So you put them all one over another in a box, but you ensure that all the ones that are not for the current item are not placed at all, only measured. That way the outer box is always measured to be as big as the biggest one of them would be. This also ensured first frame correctness since there is no remembering and setting any mutableState. p.s: .withoutPlacement is this:
Copy code
fun Modifier.withoutPlacement(): Modifier = this.layout { measurable, constraints ->
  val boxPlaceable = measurable.measure(constraints)
  layout(width = boxPlaceable.width, height = boxPlaceable.height) {}
}
e
ConstraintlLayout would still be easier, but a custom layout can do it in a single frame without specific text measurement or a hidden box with duplicate text
s
Wow, thanks for all the responses! (I left this question right before leaving the office yesterday, so sorry for the delayed response here.)
Custom layouts would probably be Okay™, but I imagine it could be a little annoying to essentially do what I get for free with Row/Column arrangements and whatnot.
My list is indeed not lazy, but even if it were, I don't think the weights would work as I envision. AFAICT, it would still suffer from the font scaling issues, and I would also need to find the correct weights to make it look decent with the widest possible time/duration.
The
TextMeasurer
approach is also one that I considered initially, but I quickly discarded it as being too expensive. On second thought, though, it's probably the right approach here. We won't have too many items in this list, either, and the ability to have first-frame correctness weighs in a lot here for me.
Thanks again for all the input! 🙌
d
I'd go with Row of Columns with `IntrinsicSize.Max`:
Copy code
Row {
   Column(Modifier.width(IntrinsicSize.Max)) {
      // time 
   }
   Column(Modifier.weight(1f)) {
      // content
   }
}
s
This ^^ would very easily break down if any of the heights just end up not matching, resulting in a crooked result where the rows don't line up.
d
you right, agree I had a similar layout, but forgot that items in columns have equals heights, so it worked in my case, but won't in this