fstanis
fstanis

Reputation: 5554

How to know the relative position of composable when giving it a custom layout?

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:

enter image description here

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

Answers (1)

Thracian
Thracian

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

Related Questions