Remove intermediate MeasurePolicy APIs from SubcomposeLayout
Test: updated existing tests
RelNote: "Removed SubcomposeLayout APIs using `intermediateMeassurePolicy`
in favor of bifercating the measure logic in the same `measurePolicy`
based on `MeasureScope.isLookingAhead`."
Change-Id: Idc6addb96070d7bcafa542baf621c31d5809e6e1
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 6b29580..0f4361b 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2362,22 +2362,9 @@
method @androidx.compose.runtime.Stable public static operator long times(long, long size);
}
- @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface SubcomposeIntermediateMeasureScope extends androidx.compose.ui.layout.SubcomposeMeasureScope {
- method public long getLookaheadConstraints();
- method public kotlin.jvm.functions.Function2<androidx.compose.ui.layout.SubcomposeMeasureScope,androidx.compose.ui.unit.Constraints,androidx.compose.ui.layout.MeasureResult> getLookaheadMeasurePolicy();
- method public long getLookaheadSize();
- method public java.util.List<androidx.compose.ui.layout.Measurable> measurablesForSlot(Object? slotId);
- method public default java.util.List<androidx.compose.ui.layout.Measurable> subcompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- property public abstract long lookaheadConstraints;
- property public abstract kotlin.jvm.functions.Function2<androidx.compose.ui.layout.SubcomposeMeasureScope,androidx.compose.ui.unit.Constraints,androidx.compose.ui.layout.MeasureResult> lookaheadMeasurePolicy;
- property public abstract long lookaheadSize;
- }
-
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
- method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeIntermediateMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> intermediateMeasurePolicy, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
- method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeIntermediateMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> intermediateMeasurePolicy, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
method public static androidx.compose.ui.layout.SubcomposeSlotReusePolicy SubcomposeSlotReusePolicy(int maxSlotsToRetainForReuse);
}
@@ -2385,9 +2372,7 @@
ctor public SubcomposeLayoutState(androidx.compose.ui.layout.SubcomposeSlotReusePolicy slotReusePolicy);
ctor public SubcomposeLayoutState();
ctor @Deprecated public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean isInLookaheadScope();
method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean isInLookaheadScope;
}
public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
index 9c932f2..b116741 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/SubcomposeLayoutSample.kt
@@ -17,27 +17,9 @@
package androidx.compose.ui.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector2D
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.UiComposable
-import androidx.compose.ui.composed
-import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.layout.intermediateLayout
-import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.launch
@Sampled
@Composable
@@ -67,97 +49,4 @@
}
}
-enum class SlotsEnum { Main, Dependent }
-
-@OptIn(ExperimentalComposeUiApi::class)
-@Sampled
-fun SubcomposeLayoutWithIntermediateMeasurePolicySample() {
- // 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) }
- })
- }
- }
-}
\ No newline at end of file
+enum class SlotsEnum { Main, Dependent }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 378d5b7..942c141 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -196,7 +196,7 @@
Box(Modifier.fillMaxSize())
}[0].measure(constraints)
val size = placeable.run { IntSize(width, height) }
- if (this is SubcomposeIntermediateMeasureScope) {
+ if (!isLookingAhead) {
actualSize = size
} else {
actualTargetSize = size
@@ -589,30 +589,12 @@
@Test
fun defaultMeasurePolicyInSubcomposeLayout() {
- var actualLookaheadSize by mutableStateOf(IntSize.Zero)
var defaultIntermediateMeasureSize by mutableStateOf(IntSize.Zero)
rule.setContent {
CompositionLocalProvider(LocalDensity provides Density(1f)) {
LookaheadScope {
SubcomposeLayout(
Modifier
- .fillMaxSize()
- .requiredSize(200.dp),
- intermediateMeasurePolicy = { constraints ->
- measurablesForSlot(Unit)[0].measure(constraints)
- actualLookaheadSize = this.lookaheadSize
- layout(0, 0) {}
- }
- ) { constraints ->
- val placeable = subcompose(Unit) {
- Box(Modifier.requiredSize(400.dp, 600.dp))
- }[0].measure(constraints)
- layout(500, 300) {
- placeable.place(0, 0)
- }
- }
- SubcomposeLayout(
- Modifier
.size(150.dp)
.intermediateLayout { measurable, _ ->
measurable
@@ -636,7 +618,6 @@
}
}
rule.runOnIdle {
- assertEquals(IntSize(500, 300), actualLookaheadSize)
assertEquals(IntSize(500, 300), defaultIntermediateMeasureSize)
}
}
@@ -1719,12 +1700,18 @@
mutableStateListOf<Int>().apply { addAll(expectedPlacementOrder1) }
var iteration by mutableStateOf(0)
+ var lookaheadConstraints by mutableStateOf<Constraints?>(null)
// Expect the default placement to be the same as lookahead
rule.setContent {
LookaheadScope {
- SubcomposeLayout(
- intermediateMeasurePolicy = { lookaheadMeasurePolicy(lookaheadConstraints) }
- ) { constraints ->
+ SubcomposeLayout { incomingConstraints ->
+ val constraints = if (isLookingAhead) {
+ lookaheadConstraints = incomingConstraints
+ incomingConstraints
+ } else {
+ lookaheadConstraints!!
+ }
+
val placeables = mutableListOf<Placeable>()
repeat(3) { id ->
subcompose(id) {
@@ -1975,20 +1962,32 @@
}
SubcomposeLayout(
Modifier
- .fillMaxSize()
- .requiredSize(200.dp),
- intermediateMeasurePolicy = { constraints ->
- assertFalse(isLookingAhead)
- measurablesForSlot(Unit)[0].measure(constraints)
- layout(0, 0) {}
- }
+ .layout { measurable, constraints ->
+ measurable.measure(constraints).run {
+ if (isLookingAhead) {
+ assertEquals(500, width)
+ assertEquals(300, height)
+ } else {
+ assertEquals(100, width)
+ assertEquals(120, height)
+ }
+ layout(width, height) {
+ place(0, 0)
+ }
+ }
+ }
) { constraints ->
- assertTrue(isLookingAhead)
val placeable = subcompose(Unit) {
Box(Modifier.requiredSize(400.dp, 600.dp))
}[0].measure(constraints)
- layout(500, 300) {
- placeable.place(0, 0)
+ if (isLookingAhead) {
+ layout(500, 300) {
+ placeable.place(0, 0)
+ }
+ } else {
+ layout(100, 120) {
+ placeable.place(0, 0)
+ }
}
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index d95d81b..619812e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -44,7 +44,6 @@
import androidx.compose.ui.node.requireOwner
import androidx.compose.ui.platform.createSubcomposition
import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
/**
@@ -83,155 +82,6 @@
* for example to use the values calculated during the measurement as params for the composition
* of the children.
*
- * 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, [measurePolicy] 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 the intermediate pass, `subcompose` will simply
- * return 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.
- *
- * Possible use cases:
- * * You need to know the constraints passed by the parent during the composition and can't solve
- * your use case with just custom [Layout] or [LayoutModifier].
- * See [androidx.compose.foundation.layout.BoxWithConstraints].
- * * You want to use the size of one child during the composition of the second child.
- * * You want to compose your items lazily based on the available size. For example you have a
- * list of 100 items and instead of composing all of them you only compose the ones which are
- * currently visible(say 5 of them) and compose next items when the component is scrolled.
- *
- * @sample androidx.compose.ui.samples.SubcomposeLayoutWithIntermediateMeasurePolicySample
- *
- * @param modifier [Modifier] to apply for the layout.
- * @param intermediateMeasurePolicy A measure policy that will be invoked during the intermediate
- * measure pass.
- * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
- */
-@ExperimentalComposeUiApi
-@Composable
-fun SubcomposeLayout(
- modifier: Modifier = Modifier,
- intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = { constraints ->
- lookaheadMeasurePolicy(constraints)
- },
- measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-) {
- SubcomposeLayout(
- state = remember { SubcomposeLayoutState() },
- modifier = modifier,
- intermediateMeasurePolicy = intermediateMeasurePolicy,
- measurePolicy = measurePolicy
- )
-}
-
-/**
- * [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.
- *
- * @sample androidx.compose.ui.samples.SubcomposeLayoutWithIntermediateMeasurePolicySample
- */
-@ExperimentalComposeUiApi
-sealed interface SubcomposeIntermediateMeasureScope : SubcomposeMeasureScope {
- /**
- * 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.
- */
- fun measurablesForSlot(slotId: Any?): List<Measurable>
-
- /**
- * The size returned in the [MeasureResult] by the measurePolicy invoked during lookahead pass.
- */
- val lookaheadSize: IntSize
-
- /**
- * 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.
- */
- val lookaheadMeasurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-
- /**
- * 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.
- */
- val lookaheadConstraints: Constraints
-
- /**
- * This function retrieves [Measurable]s 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].
- */
- override fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> =
- measurablesForSlot(slotId)
-}
-
-/**
- * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
- * for example to use the values calculated during the measurement as params for the composition
- * of the children.
- *
* Possible use cases:
* * You need to know the constraints passed by the parent during the composition and can't solve
* your use case with just custom [Layout] or [LayoutModifier].
@@ -254,66 +104,6 @@
modifier: Modifier = Modifier,
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
) {
- @OptIn(ExperimentalComposeUiApi::class)
- SubcomposeLayout(state, modifier, { lookaheadMeasurePolicy(it) }, measurePolicy)
-}
-
-/**
- * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
- * for example to use the values calculated during the measurement as params for the composition
- * of the children.
- *
- * 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, [measurePolicy] 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 the intermediate pass, `subcompose` will simply
- * return 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.
- *
- * Possible use cases:
- * * You need to know the constraints passed by the parent during the composition and can't solve
- * your use case with just custom [Layout] or [LayoutModifier].
- * See [androidx.compose.foundation.layout.BoxWithConstraints].
- * * You want to use the size of one child during the composition of the second child.
- * * You want to compose your items lazily based on the available size. For example you have a
- * list of 100 items and instead of composing all of them you only compose the ones which are
- * currently visible(say 5 of them) and compose next items when the component is scrolled.
- *
- * @param state the state object to be used by the layout.
- * @param modifier [Modifier] to apply for the layout.
- * @param intermediateMeasurePolicy A measure policy that will be invoked during the intermediate
- * measure pass.
- * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
- */
-@Composable
-@UiComposable
-@ExperimentalComposeUiApi
-fun SubcomposeLayout(
- state: SubcomposeLayoutState,
- modifier: Modifier = Modifier,
- intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = { constraints ->
- lookaheadMeasurePolicy(constraints)
- },
- measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-) {
val compositionContext = rememberCompositionContext()
val materialized = currentComposer.materialize(modifier)
val localMap = currentComposer.currentCompositionLocalMap
@@ -323,10 +113,6 @@
set(state, state.setRoot)
set(compositionContext, state.setCompositionContext)
set(measurePolicy, state.setMeasurePolicy)
- set(
- intermediateMeasurePolicy,
- state.setIntermediateMeasurePolicy
- )
set(localMap, ComposeUiNode.SetResolvedCompositionLocals)
set(materialized, ComposeUiNode.SetModifier)
}
@@ -359,6 +145,11 @@
* used during the previous measuring.
* @param content the composable content which defines the slot. It could emit multiple
* layouts, in this case the returned list of [Measurable]s will have multiple elements.
+ * **Note:** When a [SubcomposeLayout] is in a [LookaheadScope], the subcomposition only
+ * happens during the lookahead pass. In the post-lookahead/main pass, [subcompose] will
+ * return the list of [Measurable]s that were subcomposed during the lookahead pass. If the
+ * structure of the subtree emitted from [content] is dependent on incoming constraints,
+ * consider using constraints received from the lookahead pass for both passes.
*/
fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable>
}
@@ -368,7 +159,6 @@
*
* [slotReusePolicy] the policy defining what slots should be retained to be reused later.
*/
-@OptIn(ExperimentalComposeUiApi::class)
class SubcomposeLayoutState(
private val slotReusePolicy: SubcomposeSlotReusePolicy
) {
@@ -396,16 +186,6 @@
SubcomposeSlotReusePolicy(maxSlotsToRetainForReuse)
)
- /**
- * Returns whether the [SubcomposeLayout] is in a [LookaheadScope]. Intermediate measure policy
- * will be only invoked when in a [LookaheadScope].
- */
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalComposeUiApi
- @ExperimentalComposeUiApi
- val isInLookaheadScope: Boolean
- get() = state.isInLookaheadScope
-
private var _state: LayoutNodeSubcompositionsState? = null
private val state: LayoutNodeSubcompositionsState
get() = requireNotNull(_state) {
@@ -428,10 +208,6 @@
LayoutNode.((SubcomposeMeasureScope.(Constraints) -> MeasureResult)) -> Unit =
{ measurePolicy = state.createMeasurePolicy(it) }
- internal val setIntermediateMeasurePolicy:
- LayoutNode.(SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) -> Unit =
- { state.intermediateMeasurePolicy = it }
-
/**
* Composes the content for the given [slotId]. This makes the next scope.subcompose(slotId)
* call during the measure pass faster as the content is already composed.
@@ -601,18 +377,8 @@
// this map contains active slotIds (without precomposed or reusable nodes)
private val slotIdToNode = mutableMapOf<Any?, LayoutNode>()
private val scope = Scope()
- private val intermediateMeasureScope = IntermediateMeasureScopeImpl()
+ private val postLookaheadMeasureScope = PostLookaheadMeasureScopeImpl()
- /**
- * This is the intermediateMeasurePolicy that developers set in [SubcomposeLayout]. It defaults
- * to invoking [SubcomposeIntermediateMeasureScope.lookaheadMeasurePolicy].
- *
- * Note: This intermediate measure policy is only invoked when in a [LookaheadScope].
- */
- internal var intermediateMeasurePolicy:
- (SubcomposeIntermediateMeasureScope.(Constraints) -> MeasureResult) = {
- lookaheadMeasurePolicy(it)
- }
private val precomposeMap = mutableMapOf<Any?, LayoutNode>()
private val reusableSlotIdsSet = SubcomposeSlotReusePolicy.SlotIdsSet()
@@ -840,7 +606,6 @@
fun createMeasurePolicy(
block: SubcomposeMeasureScope.(Constraints) -> MeasureResult
): MeasurePolicy {
- intermediateMeasureScope.lookaheadMeasurePolicy = block
return object : LayoutNode.NoIntrinsicsMeasurePolicy(error = NoIntrinsicsMessage) {
override fun MeasureScope.measure(
measurables: List<Measurable>,
@@ -849,17 +614,14 @@
scope.layoutDirection = layoutDirection
scope.density = density
scope.fontScale = fontScale
- val isIntermediate =
- (root.layoutState == LayoutState.Measuring ||
- root.layoutState == LayoutState.LayingOut) && root.lookaheadRoot != null
- if (isIntermediate) {
- return intermediateMeasurePolicy.invoke(intermediateMeasureScope, constraints)
+ if (!isLookingAhead && root.lookaheadRoot != null) {
+ return with(postLookaheadMeasureScope) {
+ block(constraints)
+ }
} else {
currentIndex = 0
- intermediateMeasureScope.lookaheadConstraints = constraints
val result = scope.block(constraints)
val indexAfterMeasure = currentIndex
- intermediateMeasureScope.lookaheadSize = IntSize(result.width, result.height)
return object : MeasureResult {
override val width: Int
get() = result.width
@@ -1008,19 +770,15 @@
this@LayoutNodeSubcompositionsState.subcompose(slotId, content)
}
- private inner class IntermediateMeasureScopeImpl :
- SubcomposeIntermediateMeasureScope, MeasureScope by scope {
- override fun measurablesForSlot(slotId: Any?): List<Measurable> =
- slotIdToNode[slotId]?.childMeasurables ?: emptyList()
-
+ private inner class PostLookaheadMeasureScopeImpl :
+ SubcomposeMeasureScope, MeasureScope by scope {
/**
- * This is the size returned in the MeasureResult in the measure policy from the lookahead
- * pass.
+ * This function retrieves [Measurable]s 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].
*/
- override var lookaheadSize: IntSize = IntSize.Zero
- override lateinit var lookaheadMeasurePolicy:
- SubcomposeMeasureScope.(Constraints) -> MeasureResult
- override var lookaheadConstraints: Constraints = Constraints()
+ override fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> =
+ slotIdToNode[slotId]?.childMeasurables ?: emptyList()
}
}