Skip to content

Instantly share code, notes, and snippets.

@zach-klippenstein
Last active November 12, 2022 07:31
Show Gist options
  • Save zach-klippenstein/a68b75567d36e1f8e6f11dc6f6e5caf4 to your computer and use it in GitHub Desktop.
Save zach-klippenstein/a68b75567d36e1f8e6f11dc6f6e5caf4 to your computer and use it in GitHub Desktop.
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun App() {
Column {
MarxistRow {
Text("Left Text", Modifier.background(Color.Red))
Text("Right Text", Modifier.background(Color.Blue))
}
MarxistRow {
Text(
"Left Text is longer that to fit screen width for a single line so right text will not be visible",
Modifier
.background(Color.Red)
)
Text(
"Right Text",
Modifier
.background(Color.Blue)
)
}
MarxistRow {
Text("Left Text", Modifier.background(Color.Red))
Text(
"Right Text is longer that to fit screen width so left text will not be visible",
Modifier.background(Color.Blue)
)
}
MarxistRow {
Text(
"Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum",
Modifier.background(Color.Red)
)
Text(
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum",
Modifier.background(Color.Blue)
)
}
MarxistRow {
Text(
"Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum",
Modifier.background(Color.Red)
)
Text("Center Text", Modifier.background(Color.Green))
Text(
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum",
Modifier.background(Color.Blue)
)
}
MarxistRow {
Text(
"Left Text",
Modifier.background(Color.Red)
)
Text("Center Text", Modifier.background(Color.Green))
Text(
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum",
Modifier.background(Color.Blue)
)
}
}
}
/**
* > From each according to his ability, to each according to his needs
*
* https://en.wikipedia.org/wiki/From_each_according_to_his_ability,_to_each_according_to_his_needs
*/
@Composable
fun MarxistRow(
modifier: Modifier = Modifier,
verticalAlignment: Alignment.Vertical = CenterVertically,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
if (measurables.isEmpty()) return@Layout layout(0, 0) {}
// Intrinsics are cheaper than full measurement, and can be queried multiple times, unlike
// full measurement. This asks each child how wide it would be if it had unlimited width to
// fill, given it has to be at least a certain height. For simple text, this will always
// just be how wide the text is if every paragraph allowed to be a single, long line.
val intrinsicWidths = measurables.map { it.maxIntrinsicWidth(constraints.minHeight) }
// In the "worst" case, if every child is too wide, each one will get an equal share of the
// total available width. Start with that assumption, and then figure out which children
// don't actually need that much space.
val worstCaseChildWidth = constraints.maxWidth / measurables.size
val (childrenThatFit, childrenThatNeedToWrap) =
intrinsicWidths.partition { it <= worstCaseChildWidth }
// The smaller children can be packed nice and tight. If we let them be small, but still
// used the worst-case-width for larger children, the total row width wouldn't fill the
// available space – i.e. the same result you get with a
// Row(Modifier.width(IntrinsicSize.Max)) with every child given a weight(1f, fill = false)
// modifier. Instead, after packing the small children in, we can take whatever space is
// left-over and divide _that_ between the larger children. actualLargeChildWidth will be
// wider than worstCaseChildWidth, because the small children didn't use all their space.
// Setting to MAX_VALUE if all children are small ensures the math works below.
//
// TODO Just because a child was too big to fit in worstCaseChildWidth doesn't mean it will
// fill the whole actualLargeChildWidth. We should measure each child with 0 min width and
// then re-calculate the remaining available space for remaining children on every
// iteration based on how much space it officially takes.
val consumedWidth = childrenThatFit.sum()
val actualLargeChildWidth = if (childrenThatNeedToWrap.isEmpty()) Int.MAX_VALUE else {
(constraints.maxWidth - consumedWidth) / childrenThatNeedToWrap.size
}
val placeables = measurables.mapIndexed { i, measurable ->
val intrinsicWidth = intrinsicWidths[i]
val childConstraints = if (intrinsicWidth <= actualLargeChildWidth) {
// If the child is smaller than its allotted width, it should be exactly that wide.
constraints.copy(minWidth = intrinsicWidth, maxWidth = intrinsicWidth)
} else {
// Children that are too big are given equal shares of the remaining space after
// measuring the small children.
constraints.copy(minWidth = actualLargeChildWidth, maxWidth = actualLargeChildWidth)
}
measurable.measure(childConstraints)
}
// Cache the height calculation because we to use it to calculate the alignment-based y
// position for each child below.
val height = placeables.maxOf { it.height }
layout(
width = placeables.sumOf { it.width },
height = height
) {
var x = 0
placeables.forEach {
val y = verticalAlignment.align(it.height, height)
it.placeRelative(x, y)
x += it.width
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment