Stefan Oltmann
01/31/2023, 9:36 AMFlowRow
and I want the contained Composables to grow after it has been determined how much fit into one row.
Since Placeable.placeAt()
seems only to take a position a MeasurePolicy
might not be able to change the size of the Composable at all. Is that correct?
How can this be solved if not using a FlowRow
? Is there a better Composable for that?Albert Chang
01/31/2023, 9:42 AMStefan Oltmann
01/31/2023, 11:34 AMimport androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.unit.Constraints
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val measurePolicy = flowRowMeasurePolicy()
Layout(
measurePolicy = measurePolicy,
content = content,
modifier = modifier
)
}
private fun flowRowMeasurePolicy(): MeasurePolicy = MeasurePolicy { measurables, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {
val measurablesPerRow = calcMeasurablesPerRow(measurables, constraints)
var yPos = 0
for (measurablesThisRow in measurablesPerRow) {
var xPos = 0
var maxY = 0
val cumWidth = measurablesThisRow.sumOf { it.maxIntrinsicWidth(constraints.maxHeight) }
val extraSpacePerItem = (constraints.maxWidth - cumWidth) / measurablesThisRow.size
for (measurable in measurablesThisRow) {
val thisConstrains = constraints.copy(
maxWidth = measurable.maxIntrinsicWidth(constraints.maxHeight) + extraSpacePerItem
)
val placeable = measurable.measure(thisConstrains)
placeable.placeRelative(
x = xPos,
y = yPos
)
xPos += placeable.width
if (maxY < placeable.height)
maxY = placeable.height
}
yPos += maxY
}
}
}
private fun calcMeasurablesPerRow(
measurables: List<Measurable>,
constraints: Constraints
): MutableList<List<Measurable>> {
var cumWidth = 0
val measurablesPerRow = mutableListOf<List<Measurable>>()
var measurablesThisRow = mutableListOf<Measurable>()
for (measurable in measurables) {
val width = measurable.maxIntrinsicWidth(constraints.maxHeight)
val willFitInRow = cumWidth + width <= constraints.maxWidth
if (!willFitInRow) {
/* Reset */
measurablesPerRow.add(measurablesThisRow)
measurablesThisRow = mutableListOf()
cumWidth = 0
}
/* Add */
measurablesThisRow.add(measurable)
cumWidth += width
}
if (measurablesThisRow.isNotEmpty())
measurablesPerRow.add(measurablesThisRow)
return measurablesPerRow
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(300.dp)
.verticalScroll(rememberScrollState())
) {
FlowRow(
modifier = Modifier
.border(1.dp, Color.Red)
) {
for (keyword in listOf(
"animal",
"canine",
"dog",
"grass",
"pet",
"plant",
"wildlife",
"red fox",
"insect",
"something longer"
)) {
Text(
keyword,
color = Color.White,
modifier = Modifier
.minimalPadding()
.clip(defaultRoundBorderShape)
.background(Color.Blue)
.defaultPadding()
.fillMaxWidth()
)
}
}
}
Stefan Oltmann
01/31/2023, 11:41 AMStylianos Gakis
01/31/2023, 11:59 AMcalcMeasurablesPerRow
could probably return List
instead of MutableList
right? 😄
Also curious about one thing. You alter the constraints to have a maxWidth
of how much space you want them to take, but that doesn’t mean that they will fill this space right? Shouldn’t their minWidth also be adjusted so that they’re forced to fill the entire width? Maybe it works now for you due to the content itself having .fillMaxSize or something like that? Or is maxIntrinsicSize
by itself calculating exactly how much width they’ll take if you place them as they are?MR3Y
01/31/2023, 12:45 PMlayout(constraints.maxWidth, constraints.maxHeight) {}
You should instead layout children based on the size they are actually occupying(from your calculations) and that wouldn't necessarily be the passed in constraints size from the parent layout, I've faced a similar issue and posted the solution that fixed this scrolling bug in the thread https://kotlinlang.slack.com/archives/CJLTWPH7S/p1672528123554529?thread_ts=1672510349.523609&cid=CJLTWPH7SStefan Oltmann
01/31/2023, 12:47 PMmaxWidth
in the Constraint
I pass to measure()
alone has no effect without fillMaxWidth()
. This was a thing I needed a moment to understand: You can't tell the Composable/Measurable how big it should be, you just can tell it the lower and upper limits. The element itself must take this space.
I don't need to adjust the minWidth
because fillMaxWidth()
does everything I need. That's right. And even without that the maxIntrinsicSize
will calculate how much it needs without filling.
First I used minIntrinsicSize
, but that gives a smaller number for "red fox", because it calculates that "fox" can go in the second line and wraps the text. That's not what I want, so maxIntrinsicSize
is the Text
size without wrapping.Stefan Oltmann
01/31/2023, 1:05 PMimport androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Layout(content, modifier) {measurables, constraints ->
val measurablesPerRow = calcMeasurablesPerRow(measurables, constraints)
val height = measurablesPerRow.get(0).get(0).maxIntrinsicHeight(constraints.maxWidth) * measurablesPerRow.size
layout(constraints.maxWidth, height) {
var yPos = 0
for (measurablesThisRow in measurablesPerRow) {
var xPos = 0
var maxY = 0
val cumWidth = measurablesThisRow.sumOf { it.maxIntrinsicWidth(constraints.maxHeight) }
val extraSpacePerItem = (constraints.maxWidth - cumWidth) / measurablesThisRow.size
for (measurable in measurablesThisRow) {
val thisConstrains = constraints.copy(
maxWidth = measurable.maxIntrinsicWidth(constraints.maxHeight) + extraSpacePerItem
)
val placeable = measurable.measure(thisConstrains)
placeable.placeRelative(
x = xPos,
y = yPos
)
xPos += placeable.width
if (maxY < placeable.height)
maxY = placeable.height
}
yPos += maxY
}
}
}
}
private fun calcMeasurablesPerRow(
measurables: List<Measurable>,
constraints: Constraints
): MutableList<List<Measurable>> {
var cumWidth = 0
val measurablesPerRow = mutableListOf<List<Measurable>>()
var measurablesThisRow = mutableListOf<Measurable>()
for (measurable in measurables) {
val width = measurable.maxIntrinsicWidth(constraints.maxHeight)
val willFitInRow = cumWidth + width <= constraints.maxWidth
if (!willFitInRow) {
/* Reset */
measurablesPerRow.add(measurablesThisRow)
measurablesThisRow = mutableListOf()
cumWidth = 0
}
/* Add */
measurablesThisRow.add(measurable)
cumWidth += width
}
if (measurablesThisRow.isNotEmpty())
measurablesPerRow.add(measurablesThisRow)
return measurablesPerRow
}
MR3Y
01/31/2023, 1:14 PMStefan Oltmann
01/31/2023, 1:15 PMStefan Oltmann
01/31/2023, 1:16 PMMR3Y
01/31/2023, 1:17 PMStylianos Gakis
01/31/2023, 1:19 PMStefan Oltmann
01/31/2023, 1:21 PMStefan Oltmann
01/31/2023, 2:05 PMimport androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import kotlin.math.roundToInt
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Layout(content, modifier) { measurables, constraints ->
val measurablesPerRow = calcMeasurablesPerRow(measurables, constraints)
val height = measurablesPerRow.sumOf { it.maxOf { entry -> entry.size.height } }
layout(constraints.maxWidth, height) {
var yPos = 0
for (measurablesThisRow in measurablesPerRow) {
var xPos = 0
var maxY = 0
val rowWidth = measurablesThisRow.sumOf { it.size.width }
val extraWidthPerItem = (constraints.maxWidth - rowWidth) / measurablesThisRow.size.toDouble()
val extraWidthPerItemRounded = extraWidthPerItem.roundToInt()
val lostInRounding = (extraWidthPerItemRounded - extraWidthPerItem) * measurablesThisRow.size
for ((index, measurableAndSize) in measurablesThisRow.withIndex()) {
val lastItem = index == measurablesThisRow.lastIndex
val roundingCompensation = if (lastItem) lostInRounding.roundToInt() else 0
val itemConstrains = constraints.copy(
maxWidth = measurableAndSize.size.width +
extraWidthPerItemRounded - roundingCompensation
)
val placeable = measurableAndSize.measurable.measure(itemConstrains)
placeable.placeRelative(xPos, yPos)
xPos += placeable.width
if (maxY < placeable.height)
maxY = placeable.height
}
yPos += maxY
}
}
}
}
private data class MeasurableAndSize(
val measurable: Measurable,
val size: IntSize
)
private fun calcMeasurablesPerRow(
measurables: List<Measurable>,
constraints: Constraints
): MutableList<List<MeasurableAndSize>> {
var rowWidth = 0
val measurablesPerRow = mutableListOf<List<MeasurableAndSize>>()
var measurablesThisRow = mutableListOf<MeasurableAndSize>()
for (measurable in measurables) {
val size = measurable.calcMaxIntrinsicSize(constraints)
val startNewRow = rowWidth + size.width > constraints.maxWidth
if (startNewRow) {
/* Add the completed row. */
measurablesPerRow.add(measurablesThisRow)
/* Start a fresh row of Measurables */
measurablesThisRow = mutableListOf()
/* Reset the width. */
rowWidth = 0
}
/* Add */
measurablesThisRow.add(MeasurableAndSize(measurable, size))
rowWidth += size.width
}
if (measurablesThisRow.isNotEmpty())
measurablesPerRow.add(measurablesThisRow)
return measurablesPerRow
}
private fun Measurable.calcMaxIntrinsicSize(constraints: Constraints) =
IntSize(
maxIntrinsicWidth(constraints.maxHeight),
maxIntrinsicHeight(constraints.maxWidth)
)
Uchenna Okoye
03/27/2023, 8:37 PM