1

I tried to use this snipped code from Android docs, but couldn't understand how Layout Composable has parameters for measurables and constraints.

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { this: MeasureScope measurables, constraints -> 

        // ...
    }
}

I found that it is related to the below extension function but I still have no clue how it works.

local final fun MeasureScope.`<anonymous>`(
    measurables: List<Measurable>,
    constraints: Constraints
): MeasureResult

Also, wonder what is <anonymous> function name because I tried to make an extension function with the same name but I got this compile error:

name contains illegal characters: <>

Identifier not allowed in Android projects

  • You pass `MeasurePolicy` to `Layout` and inside its MeasureScope you call `measure` function which has `List` and `Constraints` param. you can pass `val measurePolicy = object:MeasurePolicy{}` to Layout. – Thracian Aug 16 '23 at 11:53

2 Answers2

2

Sounds like you're asking about how the parameters get passed (rather than how the layout works). If that's the case...

Normally, a function that acts like that might look like

@Composable inline fun Layout(
    ...
    someFunction: (List<Measurable>, Constraints) -> MeasureResult
) {
    ...
}

I get the feeling that's what you're expecting to see... You've probably heard this is known as a higher-order function, which is a function that takes other functions as arguments or returns a function.

The confusing part here, is that the actual definition of Layout looks like:

@Composable inline fun Layout(
    ...
    measurePolicy: MeasurePolicy
) {
    ...
}

MeasurePolicy is an interface with several default functions and a single abstract function:

fun MeasureScope.measure(
    measurables: List<Measurable>,
    constraints: Constraints
): MeasureResult

This type of interface is known as a SAM (single-abstract-method) interface, and can be used in the same capacity as a normal Kotlin functional type. Having it as the last parameter allows you to define that lambda after the closing paren.

The advantage to this is that when you define your lambda:

Layout(
    modifier = modifier,
    content = content
) { measurables, constraints -> 
    ... your lambda content ...
}

you're defining an implementation of that interface, and you get a couple of extra functions available inside that lambda that were defined by the interface.

As for the <anonymous> part, I suspect you're seeing decompiled source somewhere - the <anonymous> would represent that lambda as an implementation of the interface. You can think about that lambda being turned into that anonymous object as

Layout(
    modifier = modifier,
    content = content,
    measurePolicy = object: MeasurePolicy {
        fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {
            // ... your lambda content ...
        }
    }
Scott Stanchfield
  • 29,742
  • 9
  • 47
  • 65
0

In Jetpack Compose every size Modifier passes a class called Constraints which contains minWidth, maxWidth, minHeight, maxHeight params. You can refer Constraints section of answer below to see which size Modifier returns which Constraints.

https://stackoverflow.com/a/73316247/5457853

Modifier.width creates a fixed min-max SizeNode for instance.

These are done inside measure function of MeasurePolicy.

@Composable
private fun MyLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {

    val measurePolicy = object : MeasurePolicy {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {

             // Measure Measurables(content Composables)
            val placeables = measurables.map { measurable ->
                measurable.measure(constraints)
            }

            // Calculate based on your logic
            val layoutWidth = ...
            val layoutHeight= ...
            
            // Set Layout dimensions
            return layout(layoutWidth, layoutHeight) {
              // Place them vertically, horizontall, or based on custom logic
              placeables.foreaach{it.placeRelative)
            }
        }
    }

    Layout(
        modifier = modifier,
        content = content,
        measurePolicy = measurePolicy
    )
}

Then using these Constraints;

After Composition phase, during the Layout phase, the tree is traversed using the following 3 step algorithm:

1-) Measure children: A node measures its children, if any.

2-) Decide own size: Based on those measurements, a node decides on its own size.

3-) Place children: Each child node is placed relative to a node’s own position.

// 1-) We measure contents of a layout via Measurables(Composable inside layout) using Constraints(ranges that come from size or scroll Modifiers or parent Constraints)

val placeables = measurables.map { measurable ->
    measurable.measure(constraints)
}

After getting Placeables we need to decide what kind of Composables we want to build. This is a Column in this example, based on your logic you measure layout width and height differently and place Placeables accordingly. Calculate dimensions of this Layout first(dimensions of Layout).

Simplified logic for Column is to get total height of Placeables and max of Placeable if there is no size modifier, if there is any that is not infinite use it.

    // 2-) After measuring each child we decide how big this Layout/Composable should be
    // Let's say we want to make a Column we need to set width to max of content Composables
    // while sum of content Composables
    val layoutWidth = placeables.maxOf { it.width }
    val layoutHeight = placeables.sumOf { it.height }

In third step we set layout dimensions and place these Composables. Since i build a Column, i place each Placeable after another vertically.

    layout(layoutWidth, layoutHeight) {
        // 3-) Place placeables or Composables
        // In this example we place like a Column vertically

        var y = 0
        placeables.forEach { placeable: Placeable ->
            placeable.placeRelative(0, y)
            y += placeable.height
        }
    }

You can find several examples about Layout, Constraints, Size Modifiers, Constraints bounds, infinite Constraints and more in this tutorial.

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/tree/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics

Thracian
  • 43,021
  • 16
  • 133
  • 222