Hey, if anyone has a moment to take a look at this...
# compose
b
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
isn’t that just the coordinate system, i.e. “offscreen to the left” for X has to be a negative value?
b
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
i see, so you want a to be at 0,0
b
correct
t
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
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
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
I declared that the height and width when creating the layout, how else would i do it otherwise?
a
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
@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
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
I’m sorry but I don’t understand. Which incoming constraints?
a
this: `
Copy code
) { measureables, constraints ->
b
Those are vals and I can’t edit them?
a
that is correct
b
And then i set up each placeable with the widths i want?
a
you can do what you wish in your own space, yes, but your layout size must satisfy those constraints
b
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
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
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
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
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
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
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