https://kotlinlang.org logo
b

Ben Abramovitch

06/30/2021, 10:59 PM
Hey, if anyone has a moment to take a look at this issue I’m having that would be wonderful. https://stackoverflow.com/questions/68187240/custom-layout-draws-position-0-off-screen-when-content-is-larger-than-screen Essentially when I try to place a placeable off screen, instead of position 0 drawing at position 0, it’s drawing as if the total content was centered, so the further i am off screen, the further x=0 gets drawn, such as -300 I feel like there is maybe a way to handle this with AlignmentLines but i can’t figure it out if so. Also, dividing the difference by two and tacking that on seemed to work for width, but it’s not working for height even if i try to account for the statusbar/toolbars etc, and it. seems pretty hacky anyway
t

Tash

06/30/2021, 11:23 PM
isn’t that just the coordinate system, i.e. “offscreen to the left” for X has to be a negative value?
b

Ben Abramovitch

06/30/2021, 11:24 PM
If i go a b c d e f and e/f are off screen it draws b c d e
i can use the drag gesture to go left/right and see a and f, but it isn’t drawing it uncentered
t

Tash

06/30/2021, 11:25 PM
i see, so you want a to be at 0,0
b

Ben Abramovitch

06/30/2021, 11:25 PM
correct
t

Tash

06/30/2021, 11:59 PM
i see, so you’re saying when you do
width = totalWidth
in your layout, (0,0) is actually offscreen, and what you need is almost a translation of the “canvas” to bring 0,0 to the top-left of the visible window
you said you’d tried
AlignmentLine
, how’d that go?
b

Ben Abramovitch

07/01/2021, 12:04 AM
Well, if totalWidth = Screenwidth then 0 is 0 its when totalWidth > ScreenWidth it starts inching off screen I don’t understand what it wants me to do with the alignment lines. I’ve been doing translations with width / 2 as the offset but it doesn’t work accurately on the height
and i feel like having manually translate it is the wrong way
Where i set the layout height and width It can take in this map of alignment lines, but I can’t find a good explanation of what I’m supposed to do with them….
Copy code
package androidx.compose.ui.layout

 fun layout(
        width: Int,
        height: Int,
        alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
        placementBlock: Placeable.PlacementScope.() -> Unit
    ) = object : MeasureResult {
        override val width = width
        override val height = height
        override val alignmentLines = alignmentLines
        override fun placeChildren() {
            Placeable.PlacementScope.executeWithRtlMirroringValues(
                width,
                layoutDirection,
                placementBlock
            )
        }
    }
}
also i’m not sure if thats even what can solve it… but setting normal alignment on on the columns didn’t help
a

Adam Powell

07/01/2021, 12:44 AM
if a custom layout composable or layout modifier attempts to declare a size outside of the bounds of the constraints it was given, it will center its desired sized content in a space that satisfies the constraints
🙏 1
b

Ben Abramovitch

07/01/2021, 12:45 AM
I declared that the height and width when creating the layout, how else would i do it otherwise?
a

Adam Powell

07/01/2021, 12:45 AM
this allows the parent of the misbehaving element to work with a placeable that meets the constraints it was measured with so that it doesn't have to defensively account for misbehaving child measurables
looks like you're ignoring
constraints.maxWidth
in that code
b

Ben Abramovitch

07/01/2021, 12:51 AM
@Adam Powell Where? The stack example has
Copy code
constraints.copy(
                    minWidth = headerWidth,
                    maxWidth = headerWidth,
                    minHeight = height,
                    maxHeight = height,
                )
Adding a fillMaxWidth modifier here doesn’t help either
Copy code
Layout(
    modifier = Modifier.fillMaxWidth(),
    content = {
        columnList.forEach { header ->
            Box {
                headerContent(header)
            }
        }
    },
)
or here on the text box
Copy code
headerContent: @Composable (title: String) -> Unit = { title ->
    Text(title, modifier = Modifier.fillMaxWidth())
},
a

Adam Powell

07/01/2021, 1:04 AM
the code in the SO question has:
Copy code
layout(width = totalWidth, height = height) {
and those are calculated as
Copy code
val height = 45.dp.roundToPx()
val totalWidth = headerWidth * numberOfHeaders
both of which completely ignore the incoming
constraints
the contract of layout is that the width/height passed to
layout
must be in range of the
constraints
, if you violate that, your measurable gets clamped to the constraints and your placement is centered in the clamped space where your parent placed you
b

Ben Abramovitch

07/01/2021, 1:05 AM
I’m sorry but I don’t understand. Which incoming constraints?
a

Adam Powell

07/01/2021, 1:06 AM
this: `
Copy code
) { measureables, constraints ->
b

Ben Abramovitch

07/01/2021, 1:06 AM
Those are vals and I can’t edit them?
a

Adam Powell

07/01/2021, 1:06 AM
that is correct
b

Ben Abramovitch

07/01/2021, 1:06 AM
And then i set up each placeable with the widths i want?
a

Adam Powell

07/01/2021, 1:07 AM
you can do what you wish in your own space, yes, but your layout size must satisfy those constraints
b

Ben Abramovitch

07/01/2021, 1:09 AM
So i have to set the width/height on the layout ?
Copy code
Layout(
    modifier = Modifier.fillMaxWidth(headerWidth * numberOfHeaders.toFloat()).fillMaxHeight(100f),
    content = {
        columnList.forEach { header ->
            Box {
                headerContent(header)
            }
        }
    }
)
This doesn’t work either (i changed height to also be 100 further below)
Doesn’t work when i try width/height either
Same problem with this
Copy code
@Composable
fun HeaderTest(
    columnList: List<String>,
    headerWidth: Dp,
    headerContent: @Composable (title: String) -> Unit = { title ->
        Text(title, modifier = Modifier.fillMaxWidth())
    },
) {
    val numberOfHeaders = columnList.count()

    Layout(
        modifier = Modifier
            .width(headerWidth * numberOfHeaders)
            .height(100.dp)
            .fillMaxHeight()
            .fillMaxWidth(),
        content = {
            columnList.forEach { header ->
                Box {
                    headerContent(header)
                }
            }
        }
    ) { measureables, constraints ->

        val height = 100.dp
        val totalWidth = headerWidth * numberOfHeaders

        val placeWithHeader = measureables.map { measurable ->
            val constraint = constraints.copy(
                minWidth = headerWidth.roundToPx(),
                maxWidth = headerWidth.roundToPx(),
                minHeight = height.roundToPx(),
                maxHeight = height.roundToPx()
            )

            measurable.measure(constraint)
        }

        layout(width = totalWidth.roundToPx(), height = height.roundToPx()) {
            placeWithHeader.forEachIndexed { index, placeable ->
                val offset = index * headerWidth.roundToPx()
                placeable.place(offset, 0)
            }
        }
    }
}
a

Adam Powell

07/01/2021, 1:18 AM
you are still ignoring the constraints
something that would satisfy the constraints could be:
Copy code
val height = 100.dp.roundToPx().coerceIn(constraints.minHeight, constraints.maxHeight)
val totalWidth = (headerWidth * numberOfHeaders).coerceIn(constraints.minWidth, constraints.maxWidth)
b

Ben Abramovitch

07/01/2021, 1:33 AM
Okay that does work on that, but I’m still not really sure I understand what was going…. It seemed like I was doing the right thing since I was constraining the placable and I was within the bounds I was setting. I’ll have to do more reading, but I haven’t really seen that in custom layout examples. E.g this one says don’t constrain them
Copy code
Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }
But it also does the layout different….
Copy code
// Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
Lots more reading to do, but this is why i’m starting sooner!
maybe if i’d done one of the code labs….
Oh and thank you! I spent a whole day trying to narrow down what was going on and then trying things out =(
👍 1
a

Adam Powell

07/01/2021, 1:37 AM
no problem 🙂
what you're seeing here is the bridge between composition and layout. The modifiers you pass to the
Layout
composable influence but don't completely control the constraints your layout receives
when you call
measurable.measure(constraints)
, it's calling the same kind of measuring code you're writing right now, but on the child element
when you call the
layout(width, height) {
function in that block, you're telling it the desired size of the
Placeable
that the measure call returns
and then you can place your own children however you like
things like the horizontal and vertical scroll modifiers measure their child with infinite max constraints, and if the child measures larger than the max the scroll modifier was given, the scroll modifier occupies that max space and the difference becomes the amount you can scroll by
we don't have as much documentation around this as I'd like 🙂
b

Ben Abramovitch

07/01/2021, 1:42 AM
Oh that makes more sense with the layout now. I thought we were coercing it for the childen, but the same values go into the layout
Ya, well this is pretty new, not even stable yet (soon) AND i’m trying to do something custom hehe
a

Adam Powell

07/01/2021, 1:43 AM
eh, this part has been stable for a while 🙂 after years of writing Views and ViewGroups with custom measure logic we wanted to make sure doing it was a lot more straightforward here
👍 1
b

Ben Abramovitch

07/01/2021, 1:43 AM
I meant compose in general
it’s still beta (although ya, components can be more stable. than others)
Also as in, not enough time to fully document everything as you want because things are still being worked on elsewhere
3 Views