https://kotlinlang.org logo
s

sindrenm

10/08/2020, 7:11 AM
How would I go about constraining the top of one composable to the (vertical) center of another composable? I don't initially know the size of any of them. Is there a way to get the size of a composable post-measure? Thanks. simple smile
j

Jamie Craane

10/08/2020, 7:23 AM
s

sindrenm

10/08/2020, 8:33 AM
Thanks! I managed to get it to work with the
onSizeChanged
modifier. No need for a
ConstraintLayout
, though, just adding a top padding on a box is enough. simple smile
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 10:42 AM
I believe using onSizeChanged will result in a frame delay, might just be simpler to make a custom layout if you don't have to manage too many other children. But yea this sounds like exactly the sort of thing constraint layout was designed for
s

sindrenm

10/08/2020, 11:02 AM
If there was a way to do
top.linkTo(otherComposable.verticalCenter)
, then that would be best. But AFAIK there is no such thing in
ConstraintLayout.java
either, and the Compose stuff probably just mirrors/delegates to that.
Wait … there is such a thing. Let me try that real quick. I must've been blind the first time. 😕
Hmm … maybe I'm missing something, but this was harder than I thought. 😅 What I'm trying to achieve is something like the attached screenshot, which I've managed to do with this code right here:
Copy code
@Composable
@Preview
fun Test() {
    Box(Modifier.fillMaxSize()) {
        ConstraintLayout(Modifier.align(Alignment.Center)) {
            val (redBoxInTheBack, gradientBoxOnTop) = createRefs()

            Box(modifier = Modifier
                .height(200.dp)
                .width(200.dp)
                .background(Color.Red)
                .constrainAs(redBoxInTheBack) {
                    top.linkTo(<http://parent.top|parent.top>)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                }
            )

            Box(modifier = Modifier
                .height(50.dp)
                .width(100.dp)
                .background(VerticalGradient(listOf(Color.Green, Color.Blue), startY = 0f, endY = 100f))
                .constrainAs(gradientBoxOnTop) {
                    centerAround(<http://parent.top|parent.top>)
                    start.linkTo(redBoxInTheBack.start)
                }
            )
        }
    }
}
The problem I'm having is that I can constrain the center of a composable to the top of somewhere, but I can't constrain the top of something to the center of something else, if that makes sense. And so that means that if I don't have any padding around my composable, then it clips the gradient box on the top, like in the second screenshot. It clipping the top there makes sense, I guess, because the red box is supposed to be at the parent's top, but I don't see how I can put the gradient box on top and then place the red box' top on the gradient box' center. Any ideas? 🤔
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 11:56 AM
Can you post a sketch of the result you’re trying to get? I’m not sure what that is.
Do you want the gradient box to be top-start-aligned?
s

sindrenm

10/08/2020, 12:05 PM
Yeah, I want basically exactly what's in the first screenshot, but without having to explicitly add spacing around the composable itself for the gradient box to be fully visible, if that makes sense. Without a padding, the composable seems to only have the size of the red box, and thus clipping the gradient box (as you can see in the second screenshot, where only half of the gradient box is visible).
In screenshot #1, the gradient box is fully visible only because there's space around the
ConstraintLayout
(because it's centered in the outer
Box
). Second example is essentially the same thing, but where the outermost
Box
has no
Modifier.fillMaxSize()Modifier.fillMaxSize()
.
j

Jamie Craane

10/08/2020, 12:33 PM
You might want to check a custom layout in this case. As an example, the following code:
Copy code
setContent {
            MyApplicationTheme {
                Box(modifier = Modifier.height(200.dp).width(200.dp).background(Color.Gray)) {
                    Layout(children = {
                        Box(modifier = Modifier.height(150.dp).width(150.dp).background(Color.Red))
                        Box(modifier = Modifier.height(50.dp).width(100.dp).background(Color.Green))
                    }) { measurables, constraints ->
                        val placeables = measurables.map { measurable ->
                            measurable.measure(constraints)
                        }

                        layout(constraints.maxWidth, constraints.maxHeight) {
                            val second = placeables[1]
                            val first = placeables.first()
                            first.placeRelative(0, second.height / 2)
                            second.placeRelative(0, 0)
                        }
                    }
                }
            }
        }
produces the result as in the attached screenshot. You might need to tune the code because it always assumed two composables exists.
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 12:38 PM
Yea, I would probably just use a custom layout. Seems like ConstraintLayout should be able to handle this, but the DSL doesn’t support all the CL features yet?
j

Jamie Craane

10/08/2020, 12:49 PM
I am not sure if ConstraintLayout supports this, I do not see a relative positioning to position a view relative to the center of another view: https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout#RelativePositioning
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 12:52 PM
Couple notes about that code snippet though, you need to adjust the constraints to account for your extra height, and the dimensions passed to
layout()
should be the actual dimensions after measuring. E.g.:
j

Jamie Craane

10/08/2020, 12:55 PM
Yes, that is correct! (it really was a quick and dirty mockup of how this could be used). Nice additions!
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 12:55 PM
To do this with XML CL, I think you’d need to create a guide/barrier or something that is centered in the badge, and then align the top of the main one to that?
j

Jamie Craane

10/08/2020, 12:58 PM
I don’t think placement order changes the z-index. It seems placements of the children affects z-order. It might be handy if one can explicitly define (an optional) z-order when placing a placeable.
s

sindrenm

10/08/2020, 1:17 PM
Oh, nice! Thanks a ton, folks, I wasn't aware of
Layout
, but that's definitely the way to go in this case. Also, thanks for pointing out the needed size adjustments. Makes sense. 👏 ❤️
I was looking for a way to put a guideline in the vertical center of the badge there, but I couldn't find a way to do just that. Not sure if you can in XML either, though? Not that it matters, Compose all the way, d00ds. 😎
z

Zach Klippenstein (he/him) [MOD]

10/08/2020, 1:59 PM
There was a recent CL to make placement order affect drawing order, but I don’t think it made it into alpha13. https://android-review.googlesource.com/c/platform/frameworks/support/+/1435797
j

Jamie Craane

10/08/2020, 2:00 PM
Good to know!
2 Views