Jetpack Compose, Modifier.size() uses a moving scale anchor

Viewed 2627

Pay attention at how the scaling anchor changes as the image grows.

enter image description here

At first, the anchor is at the top left corner (as it should be) according to the parent layout

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    content: @Composable BoxScope.() -> Unit
) {

Then when the image width is equal to the parent width the anchor moves to the center-top. And then when the image height is greater to the parent height it moves again to the center of the image.

Here's the code I used to reproduce this: https://github.com/feresr/ComposeBugMaybe/blob/master/app/src/main/java/com/feresr/composebug/MainActivity.kt

Is this expected behavior? if so, how do I disable it? I'd like for the anchor to stay fixed in a given point (top-left) preferably. I find the behaviour strange because I'm not changing Modifier.offset() in any way.

1 Answers

Solution TL;DR

Using Modifier.wrapContentSize(Alignment, true) on a child basically tells its parent “I will ignore your constraints and position myself with this alignment)“. Failure to use Modifier.wrapContentSize on the child will just center (whenever it violates the parent constrains - this is 'compose' default behavior)

The unbounded parameter determines if this applies for the max constraints or not. In this case, it should be true since the duck gets centered when it violates the max contrains.

Full explanation

(From Adam Powell at Google):

This has to do with how Compose UI's layout system works. When a compose UI element is measured, the parent gives the child a set of constraints - min and max width and height. If the child returns a size that is not compatible with the constraints, the parent sees a size for the child clamped to be compatible with the constraints anyway.

When the parent places the misbehaving child, the child's actual position is centered with respect to the clamped size rect seen by the parent

This helps write and arrange UI elements that have hard requirements while attributing where the breakdown happened; it also helps for simple cases of placing absolute-sized elements within larger minimum-sized areas

Modifier.size - as opposed to Modifier.preferredSize - tells an element to disregard the constraints it is measured with and be the given size anyway.

So if you use Modifier.size(100.dp) inside a container that is only 50dp large, it will do what you ask for, but the parent will position that element as if it were only 50dp

That's what you're seeing here - the duck is bigger than the container, so it's getting centered by this default behavior. First along the horizontal axis, since it becomes larger than the available width first, and then along the vertical axis once it becomes too big for the available height too. Modifiers have the ability to alter this behavior by implementing a resolution strategy for when the content becomes too big (or too small) for the constraints. Modifier.wrapContentSize is an example of this; it relaxes a minimum size constraint and lets you supply options for how the element should be positioned within the minimum size

Note that wrapContentSize has an unbounded parameter that permits the element to be larger than the available size, which seems particularly relevant for your case. Apply this to the modifier of the child inside the Box

Quoted from Slack thread https://kotlinlang.slack.com/archives/CJLTWPH7S/p1609086849306600

Related