https://kotlinlang.org logo
f

frankelot

12/27/2020, 4:34 PM
This message got lost in the noise, can anyone experienced with compose can give me a hand? Not sure if this is a but I should report or something I’m doing wrong
t

Timo Drick

12/27/2020, 4:40 PM
It does not look like a change of anchor points. It looks like it centers vertically and hrizontally.
So maybe you should try to set contentAlignment = ... to the Box which contains the image
a

Adam Powell

12/27/2020, 5:20 PM
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
f

frankelot

12/27/2020, 5:24 PM
Thank you both! The default
contentAligment
for
Box
is already
Copy code
contentAlignment: Alignment = Alignment.TopStart,
a

Adam Powell

12/27/2020, 5:25 PM
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.
💯 1
f

frankelot

12/27/2020, 5:26 PM
@Adam Powell thanks, that makes sense, so this is intentional, would like to learn more about it, do you have a link to the docs where this is explained?
a

Adam Powell

12/27/2020, 5:27 PM
nope, no docs links to this so far, just the primary source in this thread 🙂
f

frankelot

12/27/2020, 5:27 PM
but the parent will position that element as if it were only 50dp
got it, any ways to avoid this then 🤔
a

Adam Powell

12/27/2020, 5:29 PM
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 🙂
f

frankelot

12/27/2020, 5:35 PM
Yup, a quick try using:
Copy code
.wrapContentSize(Alignment.TopStart, true)
seems to fix the issue 🎉
👍 1
a

Adam Powell

12/27/2020, 5:36 PM
so in the chain of modifiers, you want to relax the constraints and supply an alignment policy, then apply the unconstrained size:
Copy code
Modifier.wrapContentSize(desiredAlignment, unbounded = true)
  .size(animatedUnconstrainedSize)
👍 1
f

frankelot

12/27/2020, 5:38 PM
Oh wait, so I should be applying this to the
Image
composable, and not to the parent container
Box
?
That seems to work as well, I think I need to spend some time wrapping my head around how this works 😅
t

Timo Drick

12/27/2020, 5:39 PM
It doesn't matter because when you use it on the Box than the content size of the Box is infinite i would assume.
This makes some effects much easier to archive. 6 month ago i implemented a height animation. So when the content of a Composable changes the height will adapt to the new content height smoothly. For this i implemented a custom Layout() but now it should be easier with just this modifier.
f

frankelot

12/27/2020, 5:44 PM
I see, I’m wondering if it should be the default behaviour though. I’d rather this to be opt in
a

Adam Powell

12/27/2020, 5:44 PM
@Timo Drick if you're looking to write a custom layout for working with a single child at a time, you generally want to write a modifier instead.
Modifier.layout {}
uses the same DSL as the
Layout
composable
@frankelot which behavior, the centering as the default strategy for a child element that disobeyed its constraints?
f

frankelot

12/27/2020, 5:45 PM
Yes 🙂
a

Adam Powell

12/27/2020, 5:47 PM
we had a lot of conversations on this topic on the team and I insisted on this behavior as the default. 🙂 The reason centering is the default is that no other option is universally applicable. Left vs. right in layout is often subject to bidi/rtl layout concerns in different locales, so something that looks right to a developer in one locale might have very unexpected behavior in another.
Similarly, different containers might stack elements vertically from the bottom, so top-anchoring isn't universal either. Plus there's no good reason for a default to have different rules for width vs. height - it would just be more complicated to explain and keep in your head
Centering will always look the same at any level of tree recursion regardless of locale or container.
f

frankelot

12/27/2020, 5:50 PM
I have no idea what I’m talking about here but, why does it have to be a behaviour in the first place? Why not just clipping the view and position it according to its
offset
modifier parameters
a

Adam Powell

12/27/2020, 5:52 PM
There's no guarantee or requirement that an
offset
modifier is present. Clipping to parents is opt-in in compose and implies additional drawing behavior that is normally decoupled from layout - clipping as part of this default layout conflict resolution would introduce a dependency that doesn't need to exist
fundamentally, this behavior only comes into play when you've declared an unstoppable force against an immovable object: the child is unable to satisfy the constraints given to it by the parent.
The first decision there is whether to permit it at all, or just crash. Crashing is pretty counterproductive here for a number of practical reasons; we can't give you a retroactive stack trace that is meaningful about where the problematic state was constructed, since the measurement constraints are dependent on the overall host environment (plus measurement happens long after composition of the elements has completed). They can change in ways that are unexpected by the original component developers, so robustness with predictable behavior is a better way to go here.
The coordinate space may be (0, 0) at top left, but as mentioned above, defaulting to top left has locale and container-specific implications that can make the behavior harder to predict and reason about.
t

Timo Drick

12/27/2020, 5:58 PM
Maybe there should be a warning in the size modifier documentation about this. So i assume that you should use preferredSize and only if you know what you are doing you can use size
Ok it is already documented 😄
😁 1
a

Adam Powell

12/27/2020, 6:00 PM
that's ok, we know no one reads the docs 😄
f

frankelot

12/27/2020, 6:00 PM
Alright! that makes a lot of sense, I’m lacking context to give constructive feedback on this, I’m just getting started with compose (and really liking it so far!! despite this minor bump that had me scratching my head for a few days, part of the learning process I guess 😅)
a

Adam Powell

12/27/2020, 6:02 PM
yeah I'd like to see us illustrate this really explicitly in a codelab or something. It's one of those things that is unexpected the first time you see it, but it's a simple rule that once you know it, you can debug layouts effectively with barely a glance at the output
we made a lot of these kinds of API decisions expecting that for any given piece of compose UI code, there are three separate developers (or teams) working together, potentially without talking to one another: the developer who wrote the container that the composable is being placed in, the developer who wrote the elements being placed in a composable, and the developer writing the composable that arranges those elements.
If someone tries to place a full date picker with calendar and all into a 50dp square, it's important that someone can determine very quickly which of those three teams to send a bug to
If the date picker was forced to try to lay out its own contents in a space that is unreasonably small, someone would file that bug to the date picker's author, wait for it to get triaged, hear back, etc.
and if the parent was forced to accommodate an unexpectedly large child, the developer who wrote the whole screen would get the bug, because the whole screen's layout would get disrupted
with this default behavior, the surrounding layout is not disrupted, the date picker will be fully present (though probably too big for/overlapping/incidentally clipping in some way due to other surrounding clip behavior) and it is clear that whoever put the date picker in that particular spot probably made a mistake
the exact place where the bug is is the part of the screen that looks wrong
f

frankelot

12/27/2020, 6:10 PM
A codelab for this would be amazing 🙂 ! So, just to test my understanding. Using
.wrapContentSize(Alignment, true)
on a child basically tells its parent “I will ignore your constraints and position myself with this
aligment
)“, am I getting it right?… failing to use .`wrapContentSize`` on the child will just center (whenever it violates the parent constrains)
a

Adam Powell

12/27/2020, 6:11 PM
Yep, which is consistent with `.wrapContentSize`'s default alignment parameter being center
f

frankelot

12/27/2020, 6:11 PM
and the
unbounded
parameter determines if this applies for the
max
constrains or not
a

Adam Powell

12/27/2020, 6:11 PM
yep
t

Timo Drick

12/27/2020, 6:13 PM
I love compose 🙂 Now this effect is just 23 lines of code:
Copy code
@Composable
fun <T> CrossHeightAnimation(current: T, children: @Composable() (T) -> Unit) {
    var lastHeight by rememberState<Float?> { null }
    val height = animatedFloat(initVal = 0f)

    Box(modifier = Modifier.fillMaxWidth()
            .height(if (height.isRunning) height.value.dp else lastHeight?.dp ?: 0.dp)
            .wrapContentHeight(align = Alignment.Bottom, unbounded = true)
            .onSizeChanged { size ->
        val newHeight = with(AmbientDensity.current) { size.height.toDp().value }
        lastHeight.let { lh ->
            if (lh == null) {
                height.snapTo(newHeight)
            } else if (lh != newHeight) {
                height.snapTo(lh)
                height.animateTo(newHeight, anim = tween(500))
            }
        }
        if (lastHeight != newHeight)
            lastHeight = newHeight
    }) {
        children(current)
    }
}
f

frankelot

12/27/2020, 6:13 PM
Amazing 🙂 ! thank you so much
pretty cool effect @Timo Drick I’m using Unsplash on my pet project as well
a

Adam Powell

12/27/2020, 6:17 PM
@Timo Drick try to avoid those kinds of feedback loops like the one formed by the
onSizeChanged
->
lastHeight
->
.height()
modifier; those are the kinds of things that form layouts that don't complete in a single pass, or that relayout until they hit some sort of coalescence
some of it also might already be written for you by
Modifier.animateContentSize
as well
t

Timo Drick

12/27/2020, 6:18 PM
Yes indeed i do have problems there. Because the size is on frame behind the current visible frame.
a

Adam Powell

12/27/2020, 6:19 PM
Yep. That's generally a sign that you want to write a custom layout modifier so that you can make all of those decisions at once in a single pass rather than by using feedback loops in a chain of other layout modifiers
t

Timo Drick

12/27/2020, 6:19 PM
@Adam Powell ok i see. Compose changed and improve so fast that it is hard to be at state of the art 😄 Especcially when you do it just in your spare time.
😁 1
a

Adam Powell

12/27/2020, 6:20 PM
remember too that the layout composable/modifier DSL performs its own snapshot state observation; if you read
mutableStateOf
state inside it, the system knows when to relayout automatically
and even better, it doesn't have to recompose to do it; it only needs to do the layout/draw passes and can skip composition entirely
t

Timo Drick

12/27/2020, 6:23 PM
Ok than i will try to improve my Layout implementation. Because currently i do it wrong.
f

frankelot

12/27/2020, 7:04 PM
@Adam Powell with your permission I’m going to use your responses to this thread to answer my own stackoverflow question in case it’s useful for someone else
a

Adam Powell

12/27/2020, 7:11 PM
Go for it 🙂
f

frankelot

12/27/2020, 8:16 PM
Done! let me know if you want me to change anything
t

Timo Drick

12/27/2020, 8:34 PM
@Adam Powell the animateContentSize modifier does exactly what i need. Do not need to do any custom code.
👍 2
4 Views