{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

SubcomposeIntermediateMeasureScope

@ExperimentalComposeUiApi
public sealed interface SubcomposeIntermediateMeasureScope extends SubcomposeMeasureScope


SubcomposeIntermediateMeasureScope is the receiver scope for the intermediate measurer policy that gets invoked during the intermediate measure pass.

When in a LookaheadScope, SubcomposeLayout will be measured up to twice per frame. The two measurements will be using different measure policies and potentially different constraints.

The first measurement happens in the lookahead pass, where new layout is calculated based on the target constraints. Therefore, measurePolicy will receive the target constraints, and subcompose its content based on the target constraints. Note: Target constraints refers to the constraints that the SubcomposeLayout will receive once all the lookahead-based animations on size/constraints in the ancestor layouts have finished.

The second measurement is done in the intermediate measure pass after the lookahead pass. The intermediate measure pass allows adjustments to the measurement/placement using the pre-calculated layout information as animation targets to smooth over any any layout changes. In this measurement, intermediateMeasurePolicy will be invoked with the intermediate/animating constraints. By default, measure policy will be invoked in intermediateMeasurePolicy, and hence the same measure logic in measurePolicy with intermediate constraints will be used to measure and layout children in the intermediate pass.

Note: When measurePolicy is invoked in SubcomposeIntermediateMeasureScope, subcompose will simply retrieve the measurables associated with the given slot id based on the subcomposition during lookahead pass. This means if a given slot id has not been subcomposed in the lookahead pass, invoking subcompose during intermediate pass will result in an empty list.

import androidx.compose.runtime.Composable
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.unit.IntSize

// In this example, there is a custom modifier that animates the constraints and measures
// child with the animated constraints, as defined below.
// This modifier is built on top of `Modifier.intermediateLayout`, which
// allows access to the lookahead size of the layout. A resize animation will be kicked off
// whenever the lookahead size changes, to animate children from current size to lookahead size.
// Fixed constraints created based on the animation value will be used to measure
// child, so the child layout gradually changes its size and potentially its child's placement
// to fit within the animated constraints.
fun Modifier.animateConstraints() = composed {
    // Creates a size animation
    var sizeAnimation: Animatable<IntSize, AnimationVector2D>? by remember {
        mutableStateOf(null)
    }

    this.intermediateLayout { measurable, _ ->
        // When layout changes, the lookahead pass will calculate a new final size for the
        // child layout. This lookahead size can be used to animate the size
        // change, such that the animation starts from the current size and gradually
        // change towards `lookaheadSize`.
        if (lookaheadSize != sizeAnimation?.targetValue) {
            sizeAnimation?.run {
                launch { animateTo(lookaheadSize) }
            } ?: Animatable(lookaheadSize, IntSize.VectorConverter).let {
                sizeAnimation = it
            }
        }
        val (width, height) = sizeAnimation!!.value
        // Creates a fixed set of constraints using the animated size
        val animatedConstraints = Constraints.fixed(width, height)
        // Measure child with animated constraints.
        val placeable = measurable.measure(animatedConstraints)
        layout(placeable.width, placeable.height) {
            placeable.place(0, 0)
        }
    }
}

// In the example below, the SubcomposeLayout has a parent layout that animates its width
// between two fixed sizes using the `animateConstraints` modifier we created above.
@Composable
fun SubcomposeLayoutWithAnimatingParentLayout(
    isWide: Boolean,
    modifier: Modifier = Modifier,
    content: @Composable @UiComposable () -> Unit
) {
    // Create a MeasurePolicy to measure all children with incoming constraints and return the
    // largest width & height.
    val myMeasurePolicy = MeasurePolicy { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val maxWidth = placeables.maxOf { it.width }
        val maxHeight = placeables.maxOf { it.height }
        layout(maxWidth, maxHeight) {
            placeables.forEach { it.place(0, 0) }
        }
    }
    Box(
        Modifier
            .requiredSize(if (isWide) 400.dp else 200.dp)
            .animateConstraints()
    ) {
        // SubcomposeLayout's measurePolicy will only be invoked with lookahead constraints.
        // The parent layout in this example is animating between two fixed widths. The
        // [measurePolicy] parameter will only be called with lookahead constraints
        // (i.e. constraints for 400.dp x 400.dp or 200.dp x 200.dp depending on the state.)
        // This may cause content lambda to jump to its final size. To create a smooth
        // experience, we need to remeasure the content with the intermediate
        // constraints created by the `animateConstraints` that we built above. Therefore, we
        // need to provide a [intermediateMeasurePolicy] to define how to measure the
        // content (using the measureables of the content that was composed in [measurePolicy])
        // with intermediate constraints.
        SubcomposeLayout(
            modifier,
            intermediateMeasurePolicy = { intermediateConstraints ->
                // Retrieve the measureables for slotId = Unit, and measure them with
                // intermediate constraints using the measurePolicy we created above.
                with(myMeasurePolicy) {
                    measure(
                        measurablesForSlot(Unit),
                        intermediateConstraints
                    )
                }
            },
            measurePolicy = { constraints ->
                val measurables = subcompose(Unit) { content() }
                with(myMeasurePolicy) { measure(measurables, constraints) }
            })
    }
}

Summary

Public methods

abstract @NonNull Constraints

Returns the Constraints used in the lookahead pass.

abstract @ExtensionFunctionType @NonNull Function2<@NonNull SubcomposeMeasureScope, @NonNull Constraints, @NonNull MeasureResult>

This is the measure policy that is supplied to SubcomposeLayout in the measurePolicy parameter.

abstract @NonNull IntSize

The size returned in the MeasureResult by the measurePolicy invoked during lookahead pass.

abstract @NonNull List<@NonNull Measurable>

Returns the list of measureables associated with slotId that was subcomposed in the SubcomposeLayout's measurePolicy block during the lookahead pass.

default @NonNull List<@NonNull Measurable>
subcompose(Object slotId, @Composable @NonNull Function0<Unit> content)

This function retrieves Measurables created for slotId based on the subcomposition that happened in the lookahead pass.

Inherited methods

From androidx.compose.ui.unit.Density
abstract float

The logical density of the display.

abstract float

Current user preference for the scaling factor for fonts.

default int

Convert Dp to Int by rounding

default int

Convert Sp to Int by rounding

default @NonNull Dp

Convert Sp to Dp.

default @NonNull Dp
orgKt.toDp(int receiver)

Convert an Int pixel value to Dp.

default @NonNull Dp
orgKt.toDp(float receiver)

Convert a Float pixel value to a Dp

default @NonNull DpSize

Convert a Size to a DpSize.

default float
orgKt.toPx(@NonNull Dp receiver)

Convert Dp to pixels.

default float

Convert Sp to pixels.

default @NonNull Rect

Convert a DpRect to a Rect.

default @NonNull Size

Convert a DpSize to a Size.

default @NonNull TextUnit
orgKt.toSp(@NonNull Dp receiver)

Convert Dp to Sp.

default @NonNull TextUnit
orgKt.toSp(int receiver)

Convert an Int pixel value to Sp.

default @NonNull TextUnit
orgKt.toSp(float receiver)

Convert a Float pixel value to a Sp

From androidx.compose.ui.layout.IntrinsicMeasureScope
abstract @NonNull LayoutDirection

The LayoutDirection of the Layout or LayoutModifier using the measure scope to measure their children.

default boolean

This indicates whether the ongoing measurement is for lookahead pass.

From androidx.compose.ui.layout.MeasureScope
default @NonNull MeasureResult
layout(
    int width,
    int height,
    @NonNull Map<@NonNull AlignmentLine, @NonNull Integer> alignmentLines,
    @ExtensionFunctionType @NonNull Function1<@NonNull Placeable.PlacementScopeUnit> placementBlock
)

Sets the size and alignment lines of the measured layout, as well as the positioning block that defines the children positioning logic.

Public methods

getLookaheadConstraints

abstract @NonNull Constraints getLookaheadConstraints()

Returns the Constraints used in the lookahead pass.

Note: Using this with lookaheadMeasurePolicy will effectively skip any intermediate stages from lookahead-based layout animations. Therefore it is recommended to use Constraints passed to intermediate measure policy to measure and layout children during intermediate pass. The only exception to that should be when some of the subcompositions are conditional. In that case, a custom intermediate measure policy should ideally be provided to SubcomposeLayout. Using lookaheadConstraints with lookaheadMeasurePolicy should be considered as the last resort.

getLookaheadMeasurePolicy

abstract @ExtensionFunctionType @NonNull Function2<@NonNull SubcomposeMeasureScope, @NonNull Constraints, @NonNull MeasureResultgetLookaheadMeasurePolicy()

This is the measure policy that is supplied to SubcomposeLayout in the measurePolicy parameter. It is used in the lookahead pass, and it is also invoked by default in the intermediateMeasurePolicy for the intermediate measure pass.

During the intermediate pass, the lookaheadMeasurePolicy will receive potentially different (i.e. animating) constraints, and will subsequently remeasure and replace all children according to the new constraints.

Note: Intermediate measure pass will NOT run new subcompositions. subcompose calls in from the lookaheadMeasurePolicy in this pass will instead retrieve the measurables for the given slot based on the subcomposition from lookahead pass. In the rare case where slots are subcomposed conditionally dependent on constraints, it's recommended to provide to SubcomposeLayout a custom intermediate measure policy. A less desirable solution to this use case is to invoke lookaheadMeasurePolicy with lookaheadConstraints as its parameter, which will skip any intermediate stages straight to the lookahead sizes & positions.

getLookaheadSize

abstract @NonNull IntSize getLookaheadSize()

The size returned in the MeasureResult by the measurePolicy invoked during lookahead pass.

measurablesForSlot

abstract @NonNull List<@NonNull MeasurablemeasurablesForSlot(Object slotId)

Returns the list of measureables associated with slotId that was subcomposed in the SubcomposeLayout's measurePolicy block during the lookahead pass. If the given slotId was not used in the subcomoposition, the returned list will be empty.

subcompose

default @NonNull List<@NonNull Measurablesubcompose(Object slotId, @Composable @NonNull Function0<Unit> content)

This function retrieves Measurables created for slotId based on the subcomposition that happened in the lookahead pass. If slotId was not subcomposed in the lookahead pass, subcompose will return an emptyList.