One Off the Slack: Filling the Viewport
Ilia Voitcekhovskii asked:
I have Column > Box > Column hierarchy. All have
fillMaxSize
modifier, while box also hasverticalScroll
. As soon as box has scroll, its child column can no longer be at max size. Is this expected?
@Composable
fun TestComposable() {
val scrollState = rememberScrollState()
Column(
Modifier
.fillMaxSize()
.background(Color(0xFFA05151))
) {
Box(
Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.background(Color(0xFF1178AF))
) {
Column(
Modifier
.fillMaxSize()
.background(Color(0xFF48AF11))
) {
Text(text = "hello", color = Color(0xFFFFFFFF))
}
}
}
}
Google’s Adam Powell said, in short, “yes”:
This is expected. The content of a scrolling region has infinite max size, and it is not possible to fill an infinite max size.
Illa was wondering how to achieve the same basic logic as android:fillViewport
in
the classic View
system.
Adam offered:
var constraints by remember { mutableStateOf(Constraints()) }
Column(
Modifier
.fillMaxSize()
.onMeasureConstraints { constraints = it }
.verticalScroll(rememberScrollState())
) {
Box(
Modifier
.fillMaxWidth()
.constrainSize { constraints }
.background(Color.Blue)
)
Box(Modifier.fillMaxWidth().height(100.dp).background(Color.Green))
}
// elsewhere at the top level...
fun Modifier.onMeasureConstraints(
block: (Constraints) -> Unit
) = layout { measurable, constraints ->
// record the constraints *before* measuring so that they're available during recursive measurement
block(constraints)
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
}
fun Modifier.constrainSize(
getConstraints: () -> Constraints
) = layout { measurable, constraints ->
val placeable = measurable.measure(constraints.constrain(getConstraints()))
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
}
Adam elaborated:
then the two modifiers there and the general technique are reusable wherever else you might want to apply it
you could do things like
fillMaxHeight(0.9)
to make sure that there’s always a bit of scrollable content still visible as opposed to filling the entire viewport, etc.
it’s specifically important that
constrainSize
definesgetConstraints
as a function and not a raw value, since invoking that function performs a state read of the previously written constraints, invalidating the measurement of theconstrainSize
‘d element when the input constraints change, but without invalidating the composition that created it
Illa had questions about that last bit:
I assume that everything that reads from state container will be invalidated when value inside it changes, right? So the difference here is that if i read raw value - then whole scope that uses
constraints
state needs to be invalidated (which in this example starts withColumn
) since there no other way to trigger change, but if there is a function inside layout that reads it, then that invalidation scope is just that layout block - so onlyBox
and its children will get invalidated?
Adam responded:
Yes. By using a lambda given to the layout modifier to read the state, you invalidate the layout that calls that lambda function rather than the composition
Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!