LookaheadScope
Common
Component in Compose Ui
[LookaheadScope] creates a scope in which all layouts will first determine their destination layout through a lookahead pass, followed by an approach pass to run the measurement and placement approach defined in [approachLayout] or [ApproachLayoutModifierNode], in order to gradually reach the destination.
Last updated:
Installation
dependencies {
implementation("androidx.compose.ui:ui:1.8.0-alpha04")
}
Overloads
@UiComposable
@Composable
fun LookaheadScope(content: @Composable @UiComposable LookaheadScope.() -> Unit)
Parameters
name | description |
---|---|
content | The child composable to be laid out. |
Code Example
LookaheadLayoutCoordinatesSample
@OptIn(ExperimentalAnimatableApi::class)
@Composable
fun LookaheadLayoutCoordinatesSample() {
/**
* Creates a custom implementation of ApproachLayoutModifierNode to approach the placement of
* the layout using an animation.
*/
class AnimatedPlacementModifierNode(var lookaheadScope: LookaheadScope) :
ApproachLayoutModifierNode, Modifier.Node() {
// Creates an offset animation, the target of which will be known during placement.
val offsetAnimation: DeferredTargetAnimation<IntOffset, AnimationVector2D> =
DeferredTargetAnimation(IntOffset.VectorConverter)
override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
// Since we only animate the placement here, we can consider measurement approach
// complete.
return false
}
// Returns true when the offset animation is in progress, false otherwise.
override fun Placeable.PlacementScope.isPlacementApproachInProgress(
lookaheadCoordinates: LayoutCoordinates
): Boolean {
val target =
with(lookaheadScope) {
lookaheadScopeCoordinates.localLookaheadPositionOf(lookaheadCoordinates).round()
}
offsetAnimation.updateTarget(target, coroutineScope)
return !offsetAnimation.isIdle
}
override fun ApproachMeasureScope.approachMeasure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
val placeable = measurable.measure(constraints)
return layout(placeable.width, placeable.height) {
val coordinates = coordinates
if (coordinates != null) {
// Calculates the target offset within the lookaheadScope
val target =
with(lookaheadScope) {
lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates).round()
}
// Uses the target offset to start an offset animation
val animatedOffset = offsetAnimation.updateTarget(target, coroutineScope)
// Calculates the *current* offset within the given LookaheadScope
val placementOffset =
with(lookaheadScope) {
lookaheadScopeCoordinates
.localPositionOf(coordinates, Offset.Zero)
.round()
}
// Calculates the delta between animated position in scope and current
// position in scope, and places the child at the delta offset. This puts
// the child layout at the animated position.
val (x, y) = animatedOffset - placementOffset
placeable.place(x, y)
} else {
placeable.place(0, 0)
}
}
}
}
// Creates a custom node element for the AnimatedPlacementModifierNode above.
data class AnimatePlacementNodeElement(val lookaheadScope: LookaheadScope) :
ModifierNodeElement<AnimatedPlacementModifierNode>() {
override fun update(node: AnimatedPlacementModifierNode) {
node.lookaheadScope = lookaheadScope
}
override fun create(): AnimatedPlacementModifierNode {
return AnimatedPlacementModifierNode(lookaheadScope)
}
}
val colors = listOf(Color(0xffff6f69), Color(0xffffcc5c), Color(0xff264653), Color(0xff2a9d84))
var isInColumn by remember { mutableStateOf(true) }
LookaheadScope {
// Creates movable content containing 4 boxes. They will be put either in a [Row] or in a
// [Column] depending on the state
val items = remember {
movableContentOf {
colors.forEach { color ->
Box(
Modifier.padding(15.dp)
.size(100.dp, 80.dp)
.then(AnimatePlacementNodeElement(this))
.background(color, RoundedCornerShape(20))
)
}
}
}
Box(modifier = Modifier.fillMaxSize().clickable { isInColumn = !isInColumn }) {
// As the items get moved between Column and Row, their positions in LookaheadScope
// will change. The `animatePlacementInScope` modifier created above will
// observe that final position change via `localLookaheadPositionOf`, and create
// a position animation.
if (isInColumn) {
Column(Modifier.fillMaxSize()) { items() }
} else {
Row { items() }
}
}
}
}