Reputation: 5554
I'm trying to create a custom Compose layout where elements (primarily inside a Column
) have their maximum width constrained depending on their position inside the parent.
I've come up with the following Modifier
that does it:
fun Modifier.widthDependingOnTop() = composed {
var parentY by remember { mutableFloatStateOf(Float.NaN) }
onPlaced {
parentY = it.positionInParent().y
}.layout { measurable, constraints ->
val placeable = measurable.measure(
if (parentY <= 200f) {
constraints.copy(maxWidth = 100)
} else {
constraints
}
)
layout(placeable.width, placeable.height) {
placeable.placeRelative(x = 0, y = 0)
}
}
}
I used it the following way:
Column(horizontalAlignment = Alignment.CenterHorizontally) {
repeat(5) {
Row(Modifier.widthDependingOnTop().background(Color.Red)) {
Text("lorem ipsum")
}
}
}
And it produced the expected result:
However, this isn't ideal because the content flashes briefly when it's displayed - I understand this is because I'm waiting for onPlaced
to know the coordinates inside the parent, after which I modify the width (potentially).
I could avoid this if there's a way inside layout{}
to know the position where the composable will appear inside the parent, however I wasn't able to find anything akin to that. Is there a way to do this? Perhaps a modifier should be applied to the parent Column
and not the children?
Upvotes: 1
Views: 291
Reputation: 67248
You can move this logic inside Layout
if you don't want your Composable to be recomposed when size changes as in this answer.
However if you wish to keep using Modifier you can delay drawing one or few frames by using Modiifer.drawWithContent{drawContent()}
with a condition.
To wait one frame you can call awaitFrame
inside corotineScope but if you still see flash you can set some fixed number to delay draw phase as
fun Modifier.widthDependingOnTop() = composed {
var parentY by remember { mutableFloatStateOf(Float.NaN) }
var draw by remember {
mutableStateOf(false)
}
LaunchedEffect(parentY) {
if (parentY.isNaN().not()) {
// delay(50)
awaitFrame()
draw = true
}
}
onPlaced {
parentY = it.positionInParent().y
}
.drawWithContent {
if (draw) {
drawContent()
}
}
.layout { measurable, constraints ->
val placeable = measurable.measure(
if (parentY <= 200f) {
constraints.copy(maxWidth = 100)
} else {
constraints
}
)
layout(placeable.width, placeable.height) {
placeable.placeRelative(x = 0, y = 0)
}
}
}
Upvotes: 1