androidx.compose.ui.layout
Interfaces
ApproachIntrinsicMeasureScope |
The receiver scope of a layout's intrinsic approach measurements lambdas. |
Cmn
|
ApproachLayoutModifierNode |
|
Cmn
|
ApproachMeasureScope |
|
Cmn
|
BeyondBoundsLayout |
Layout extra items in the specified direction. |
Cmn
|
BeyondBoundsLayout.BeyondBoundsScope |
The scope used in |
Cmn
|
BeyondBoundsLayoutProviderModifierNode |
Provides a |
Cmn
|
ContentScale |
Represents a rule to apply to scale a source rectangle to be inscribed into a destination |
Cmn
|
GraphicLayerInfo |
The info about the graphics layers used by tooling. |
android
|
IntrinsicMeasurable |
A part of the composition that can be measured. |
Cmn
|
IntrinsicMeasureScope |
The receiver scope of a layout's intrinsic measurements lambdas. |
Cmn
|
LayoutCoordinates |
A holder of the measured bounds for the |
Cmn
|
LayoutIdParentData |
Can be implemented by values used as parent data to make them usable as tags. |
Cmn
|
LayoutInfo |
The public information about the layouts used internally as nodes in the Compose UI hierarchy. |
Cmn
|
LayoutModifier |
A |
Cmn
|
LookaheadScope |
|
Cmn
|
Measurable |
A part of the composition that can be measured. |
Cmn
|
MeasurePolicy |
Defines the measure and layout behavior of a |
Cmn
|
MeasureResult |
Interface holding the size and alignment lines of the measured layout, as well as the children positioning logic. |
Cmn
|
MeasureScope |
The receiver scope of a layout's measure lambda. |
Cmn
|
Measured |
A |
Cmn
|
MultiContentMeasurePolicy |
Defines the measure and layout behavior of a |
Cmn
|
OnGloballyPositionedModifier |
A modifier whose |
Cmn
|
OnPlacedModifier |
A modifier whose |
Cmn
|
OnRemeasuredModifier |
A modifier whose |
Cmn
|
ParentDataModifier |
Cmn
|
|
PinnableContainer |
Represents a container which can be pinned when the content of this container is important. |
Cmn
|
PinnableContainer.PinnedHandle |
This is an object returned by |
Cmn
|
RectRulers |
A collection of |
Cmn
|
Remeasurement |
This object is associated with a layout node and allows to execute some extra measure/layout actions which are needed for some complex layouts. |
Cmn
|
RemeasurementModifier |
A |
Cmn
|
RulerScope |
A scope used in |
Cmn
|
SubcomposeLayoutState.PausedPrecomposition |
A |
Cmn
|
SubcomposeLayoutState.PrecomposedSlotHandle |
Instance of this interface is returned by |
Cmn
|
SubcomposeMeasureScope |
The receiver scope of a |
Cmn
|
SubcomposeSlotReusePolicy |
This policy allows |
Cmn
|
WindowInsetsAnimation |
Provides properties related to animating |
Cmn
|
WindowInsetsRulers |
Contains rulers used for window insets. |
Cmn
|
Classes
AlignmentLine |
Defines an offset line that can be used by parent layouts to align and position their children. |
Cmn
|
BeyondBoundsLayout.LayoutDirection |
The direction (from the visible bounds) that a |
Cmn
|
FixedScale |
|
Cmn
|
HorizontalAlignmentLine |
A horizontal |
Cmn
|
HorizontalRuler |
A horizontal |
Cmn
|
LayoutBoundsHolder |
An object which holds on to a (potentially) mutating |
Cmn
|
ModifierInfo |
Used by tooling to examine the modifiers on a |
Cmn
|
Placeable |
A |
Cmn
|
Placeable.PlacementScope |
Receiver scope that permits explicit placement of a |
Cmn
|
Ruler |
A line that can be used to align layout children inside a |
Cmn
|
ScaleFactor |
Holds 2 dimensional scaling factors for horizontal and vertical axes |
Cmn
|
SubcomposeLayoutState |
State used by |
Cmn
|
SubcomposeSlotReusePolicy.SlotIdsSet |
Set containing slot ids currently available to reuse. |
Cmn
|
VerticalAlignmentLine |
A vertical |
Cmn
|
VerticalRuler |
A vertical |
Cmn
|
Objects
Annotations
Top-level functions summary
inline Unit |
@Composable
|
Cmn
|
inline Unit |
@UiComposable
|
Cmn
|
inline Unit |
@UiComposable
|
Cmn
|
Unit |
@UiComposable
|
Cmn
|
Unit |
@ComposableThis function is deprecated. This API is unsafe for UI performance at scale - using it incorrectly will lead to exponential performance issues. |
Cmn
|
RectRulers |
Creates a |
Cmn
|
inline ScaleFactor |
ScaleFactor(scaleX: Float, scaleY: Float)Constructs a |
Cmn
|
Unit |
@ComposableAnalogue of |
Cmn
|
Unit |
@ComposableAnalogue of |
Cmn
|
SubcomposeSlotReusePolicy |
SubcomposeSlotReusePolicy(maxSlotsToRetainForReuse: Int)Creates |
Cmn
|
ScaleFactor |
lerp(start: ScaleFactor, stop: ScaleFactor, fraction: Float)Linearly interpolate between two |
Cmn
|
DelegatableNode |
onVisibilityChangedNode(Creates a |
Cmn
|
Extension functions summary
Modifier |
Modifier.approachLayout(Creates an approach layout intended to help gradually approach the destination layout calculated in the lookahead pass. |
Cmn
|
Rect |
Returns the bounding box of the child in the parent's content area, including any clipping done with respect to the parent. |
Cmn
|
Rect |
The boundaries of this layout inside the root composable. |
Cmn
|
Rect |
LayoutCoordinates.boundsInWindow(clipBounds: Boolean)The boundaries of this layout relative to the window's origin. |
Cmn
|
operator Size |
Size.div(scaleFactor: ScaleFactor)Division operator with |
Cmn
|
LayoutCoordinates |
Walks up the |
Cmn
|
List<RectRulers> |
Returns a List of |
Cmn
|
RectRulers |
RectRulers.Companion.innermostOf(vararg rulers: RectRulers)Merges multiple |
Cmn
|
Modifier |
Modifier.layout(measure: MeasureScope.(Measurable, Constraints) -> MeasureResult)Creates a |
Cmn
|
Modifier |
Modifier.layoutBounds(holder: LayoutBoundsHolder)This will map the |
Cmn
|
Modifier |
Tag the element with |
Cmn
|
LayoutCoordinates |
LookaheadScope.lookaheadScopeCoordinates(Obtains the |
Cmn
|
Modifier |
Modifier.onFirstVisible(Registers a callback to monitor when the node is first inside of the viewport of the window or not. |
Cmn
|
Modifier |
Modifier.onGloballyPositioned(Invoke |
Cmn
|
Modifier |
Modifier.onLayoutRectChanged(Invokes |
Cmn
|
Modifier |
Modifier.onPlaced(onPlaced: (LayoutCoordinates) -> Unit)Invoke |
Cmn
|
Modifier |
Modifier.onSizeChanged(onSizeChanged: (IntSize) -> Unit)Invoked with the size of the modified Compose UI element when the element is first measured or when the size of the element changes. |
Cmn
|
Modifier |
Modifier.onVisibilityChanged(Registers a callback to monitor whether or not the node is inside of the viewport of the window or not. |
Cmn
|
RectRulers |
RectRulers.Companion.outermostOf(vararg rulers: RectRulers)Merges multiple |
Cmn
|
Offset |
Returns the position of the top-left in the parent's content area or (0, 0) for the root. |
Cmn
|
Offset |
The position of this layout inside the root composable. |
Cmn
|
Offset |
The position of this layout relative to the window. |
Cmn
|
Offset |
The position of this layout on the device's screen. |
Cmn
|
DelegatableNode.RegistrationHandle |
DelegatableNode.registerOnGlobalLayoutListener(Registers a |
Cmn
|
DelegatableNode.RegistrationHandle |
DelegatableNode.registerOnLayoutRectChanged(Registers a |
Cmn
|
inline ScaleFactor |
ScaleFactor.takeOrElse(block: () -> ScaleFactor)If this |
Cmn
|
operator Size |
Size.times(scaleFactor: ScaleFactor)Multiplication operator with |
Cmn
|
operator Size |
ScaleFactor.times(size: Size)Multiplication operator with |
Cmn
|
Top-level properties summary
HorizontalAlignmentLine |
|
Cmn
|
HorizontalAlignmentLine |
|
Cmn
|
ProvidableCompositionLocal<PinnableContainer?> |
Use this composition local to get the |
Cmn
|
ProvidableModifierLocal<BeyondBoundsLayout?> |
A modifier local that provides access to a |
Cmn
|
Extension properties summary
Boolean |
|
Cmn
|
Boolean |
|
Cmn
|
Any? |
Retrieves the tag associated to a composable with the |
Cmn
|
View? |
Return the owner as a View from the associated LayoutNode. |
android
|
Top-level functions
Layout
@Composable
@UiComposable
inline fun Layout(modifier: Modifier = Modifier, measurePolicy: MeasurePolicy): Unit
Layout is the main core component for layout for "leaf" nodes. It can be used to measure and position zero children.
The measurement, layout and intrinsic measurement behaviours of this layout will be defined by the measurePolicy instance. See MeasurePolicy for more details.
For a composable able to define its content according to the incoming constraints, see androidx.compose.foundation.layout.BoxWithConstraints.
Example usage:
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints // We build a layout that will occupy twice as much space as its children, // and will position them to be bottom right aligned. Layout(content) { measurables, constraints -> // measurables contains one element corresponding to each of our layout children. // constraints are the constraints that our parent is currently measuring us with. val childConstraints = Constraints( minWidth = constraints.minWidth / 2, minHeight = constraints.minHeight / 2, maxWidth = if (constraints.hasBoundedWidth) { constraints.maxWidth / 2 } else { Constraints.Infinity }, maxHeight = if (constraints.hasBoundedHeight) { constraints.maxHeight / 2 } else { Constraints.Infinity }, ) // We measure the children with half our constraints, to ensure we can be double // the size of the children. val placeables = measurables.map { it.measure(childConstraints) } val layoutWidth = (placeables.maxByOrNull { it.width }?.width ?: 0) * 2 val layoutHeight = (placeables.maxByOrNull { it.height }?.height ?: 0) * 2 // We call layout to set the size of the current layout and to provide the positioning // of the children. The children are placed relative to the current layout place. layout(layoutWidth, layoutHeight) { placeables.forEach { it.placeRelative(layoutWidth - it.width, layoutHeight - it.height) } } }
Example usage with custom intrinsic measurements:
import androidx.compose.ui.layout.IntrinsicMeasurable import androidx.compose.ui.layout.IntrinsicMeasureScope import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints // We build a layout that will occupy twice as much space as its children, // and will position them to be bottom right aligned. val measurePolicy = object : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints, ): MeasureResult { // measurables contains one element corresponding to each of our layout children. // constraints are the constraints that our parent is currently measuring us with. val childConstraints = Constraints( minWidth = constraints.minWidth / 2, minHeight = constraints.minHeight / 2, maxWidth = if (constraints.hasBoundedWidth) { constraints.maxWidth / 2 } else { Constraints.Infinity }, maxHeight = if (constraints.hasBoundedHeight) { constraints.maxHeight / 2 } else { Constraints.Infinity }, ) // We measure the children with half our constraints, to ensure we can be double // the size of the children. val placeables = measurables.map { it.measure(childConstraints) } val layoutWidth = (placeables.maxByOrNull { it.width }?.width ?: 0) * 2 val layoutHeight = (placeables.maxByOrNull { it.height }?.height ?: 0) * 2 // We call layout to set the size of the current layout and to provide the // positioning // of the children. The children are placed relative to the current layout place. return layout(layoutWidth, layoutHeight) { placeables.forEach { it.placeRelative(layoutWidth - it.width, layoutHeight - it.height) } } } // The min intrinsic width of this layout will be twice the largest min intrinsic // width of a child. Note that we call minIntrinsicWidth with h / 2 for children, // since we should be double the size of the children. override fun IntrinsicMeasureScope.minIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int, ) = (measurables.map { it.minIntrinsicWidth(height / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int, ) = (measurables.map { it.minIntrinsicHeight(width / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.maxIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int, ) = (measurables.map { it.maxIntrinsicWidth(height / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int, ) = (measurables.map { it.maxIntrinsicHeight(width / 2) }.maxByOrNull { it } ?: 0) * 2 } Layout(content = content, measurePolicy = measurePolicy)
| Parameters | |
|---|---|
modifier: Modifier = Modifier |
Modifiers to be applied to the layout. |
measurePolicy: MeasurePolicy |
The policy defining the measurement and positioning of the layout. |
| See also | |
|---|---|
Layout |
|
MeasurePolicy |
|
BoxWithConstraints |
Layout
@UiComposable
@Composable
inline fun Layout(
content: @Composable @UiComposable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
): Unit
Layout is the main core component for layout. It can be used to measure and position zero or more layout children.
The measurement, layout and intrinsic measurement behaviours of this layout will be defined by the measurePolicy instance. See MeasurePolicy for more details.
For a composable able to define its content according to the incoming constraints, see androidx.compose.foundation.layout.BoxWithConstraints.
Example usage:
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints // We build a layout that will occupy twice as much space as its children, // and will position them to be bottom right aligned. Layout(content) { measurables, constraints -> // measurables contains one element corresponding to each of our layout children. // constraints are the constraints that our parent is currently measuring us with. val childConstraints = Constraints( minWidth = constraints.minWidth / 2, minHeight = constraints.minHeight / 2, maxWidth = if (constraints.hasBoundedWidth) { constraints.maxWidth / 2 } else { Constraints.Infinity }, maxHeight = if (constraints.hasBoundedHeight) { constraints.maxHeight / 2 } else { Constraints.Infinity }, ) // We measure the children with half our constraints, to ensure we can be double // the size of the children. val placeables = measurables.map { it.measure(childConstraints) } val layoutWidth = (placeables.maxByOrNull { it.width }?.width ?: 0) * 2 val layoutHeight = (placeables.maxByOrNull { it.height }?.height ?: 0) * 2 // We call layout to set the size of the current layout and to provide the positioning // of the children. The children are placed relative to the current layout place. layout(layoutWidth, layoutHeight) { placeables.forEach { it.placeRelative(layoutWidth - it.width, layoutHeight - it.height) } } }
Example usage with custom intrinsic measurements:
import androidx.compose.ui.layout.IntrinsicMeasurable import androidx.compose.ui.layout.IntrinsicMeasureScope import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints // We build a layout that will occupy twice as much space as its children, // and will position them to be bottom right aligned. val measurePolicy = object : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints, ): MeasureResult { // measurables contains one element corresponding to each of our layout children. // constraints are the constraints that our parent is currently measuring us with. val childConstraints = Constraints( minWidth = constraints.minWidth / 2, minHeight = constraints.minHeight / 2, maxWidth = if (constraints.hasBoundedWidth) { constraints.maxWidth / 2 } else { Constraints.Infinity }, maxHeight = if (constraints.hasBoundedHeight) { constraints.maxHeight / 2 } else { Constraints.Infinity }, ) // We measure the children with half our constraints, to ensure we can be double // the size of the children. val placeables = measurables.map { it.measure(childConstraints) } val layoutWidth = (placeables.maxByOrNull { it.width }?.width ?: 0) * 2 val layoutHeight = (placeables.maxByOrNull { it.height }?.height ?: 0) * 2 // We call layout to set the size of the current layout and to provide the // positioning // of the children. The children are placed relative to the current layout place. return layout(layoutWidth, layoutHeight) { placeables.forEach { it.placeRelative(layoutWidth - it.width, layoutHeight - it.height) } } } // The min intrinsic width of this layout will be twice the largest min intrinsic // width of a child. Note that we call minIntrinsicWidth with h / 2 for children, // since we should be double the size of the children. override fun IntrinsicMeasureScope.minIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int, ) = (measurables.map { it.minIntrinsicWidth(height / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int, ) = (measurables.map { it.minIntrinsicHeight(width / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.maxIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int, ) = (measurables.map { it.maxIntrinsicWidth(height / 2) }.maxByOrNull { it } ?: 0) * 2 override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int, ) = (measurables.map { it.maxIntrinsicHeight(width / 2) }.maxByOrNull { it } ?: 0) * 2 } Layout(content = content, measurePolicy = measurePolicy)
| Parameters | |
|---|---|
content: @Composable @UiComposable () -> Unit |
The children composable to be laid out. |
modifier: Modifier = Modifier |
Modifiers to be applied to the layout. |
measurePolicy: MeasurePolicy |
The policy defining the measurement and positioning of the layout. |
| See also | |
|---|---|
Layout |
|
MeasurePolicy |
|
BoxWithConstraints |
Layout
@UiComposable
@Composable
inline fun Layout(
contents: List<@Composable @UiComposable () -> Unit>,
modifier: Modifier = Modifier,
measurePolicy: MultiContentMeasurePolicy
): Unit
Layout is the main core component for layout. It can be used to measure and position zero or more layout children.
This overload accepts a list of multiple composable content lambdas, which allows treating measurables put into different content lambdas differently - measure policy will provide a list of lists of Measurables, not just a single list. Such list has the same size as the list of contents passed into Layout and contains the list of measurables of the corresponding content lambda in the same order.
Note that layouts emitted as part of all contents lambdas will be added as a direct children for this Layout. This means that if you set a custom z index on some children, the drawing order will be calculated as if they were all provided as part of one lambda.
Example usage:
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.layout // We can provide pass a list of two composable lambdas in order to be able to treat // measureables from each lambda differently. Layout(listOf(content1, content2)) { (content1Measurables, content2Measurables), constraints -> val content1Placeables = content1Measurables.map { it.measure(constraints) } val content2Placeables = content2Measurables.map { it.measure(constraints) } layout(constraints.maxWidth, constraints.maxHeight) { var currentX = 0 var currentY = 0 var currentMaxHeight = 0 // we place placeables from content1 as a first line content1Placeables.forEach { it.place(currentX, currentY) currentX += it.width currentMaxHeight = maxOf(currentMaxHeight, it.height) } currentX = 0 currentY = currentMaxHeight // and placeables from content2 composable as a second line content2Placeables.forEach { it.place(currentX, currentY) currentX += it.width } } }
| Parameters | |
|---|---|
contents: List<@Composable @UiComposable () -> Unit> |
The list of children composable contents to be laid out. |
modifier: Modifier = Modifier |
Modifiers to be applied to the layout. |
measurePolicy: MultiContentMeasurePolicy |
The policy defining the measurement and positioning of the layout. |
| See also | |
|---|---|
Layout |
for a simpler use case when you have only one content lambda. |
LookaheadScope
@UiComposable
@Composable
fun LookaheadScope(content: @Composable @UiComposable LookaheadScope.() -> Unit): Unit
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.
Note: LookaheadScope does not introduce a new Layout to the content passed in. All the Layouts in the content will have the same parent as they would without LookaheadScope.
import androidx.compose.animation.core.AnimationVector2D import androidx.compose.animation.core.DeferredTargetAnimation import androidx.compose.animation.core.VectorConverter import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ApproachLayoutModifierNode import androidx.compose.ui.layout.ApproachMeasureScope import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.LookaheadScope import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layout import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round /** * 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() } } } }
| Parameters | |
|---|---|
content: @Composable @UiComposable LookaheadScope.() -> Unit |
The child composable to be laid out. |
| See also | |
|---|---|
ApproachLayoutModifierNode |
|
approachLayout |
MultiMeasureLayout
@Composable
@UiComposable
funMultiMeasureLayout(
modifier: Modifier = Modifier,
content: @Composable @UiComposable () -> Unit,
measurePolicy: MeasurePolicy
): Unit
ScaleFactor
inline fun ScaleFactor(scaleX: Float, scaleY: Float): ScaleFactor
Constructs a ScaleFactor from the given x and y scale values
SubcomposeLayout
@Composable
fun SubcomposeLayout(
modifier: Modifier = Modifier,
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
): Unit
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
LayoutorLayoutModifier. Seeandroidx.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.
import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.unit.IntSize // enum class SlotsEnum { Main, Dependent } SubcomposeLayout { constraints -> val mainPlaceables = subcompose(SlotsEnum.Main, mainContent).map { it.measure(constraints) } val maxSize = mainPlaceables.fold(IntSize.Zero) { currentMax, placeable -> IntSize( width = maxOf(currentMax.width, placeable.width), height = maxOf(currentMax.height, placeable.height), ) } layout(maxSize.width, maxSize.height) { mainPlaceables.forEach { it.placeRelative(0, 0) } subcompose(SlotsEnum.Dependent) { dependentContent(maxSize) } .forEach { it.measure(constraints).placeRelative(0, 0) } } }
| Parameters | |
|---|---|
modifier: Modifier = Modifier |
|
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult |
Measure policy which provides ability to subcompose during the measuring. |
SubcomposeLayout
@Composable
@UiComposable
fun SubcomposeLayout(
state: SubcomposeLayoutState,
modifier: Modifier = Modifier,
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
): Unit
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
LayoutorLayoutModifier. Seeandroidx.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.
import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.unit.IntSize // enum class SlotsEnum { Main, Dependent } SubcomposeLayout { constraints -> val mainPlaceables = subcompose(SlotsEnum.Main, mainContent).map { it.measure(constraints) } val maxSize = mainPlaceables.fold(IntSize.Zero) { currentMax, placeable -> IntSize( width = maxOf(currentMax.width, placeable.width), height = maxOf(currentMax.height, placeable.height), ) } layout(maxSize.width, maxSize.height) { mainPlaceables.forEach { it.placeRelative(0, 0) } subcompose(SlotsEnum.Dependent) { dependentContent(maxSize) } .forEach { it.measure(constraints).placeRelative(0, 0) } } }
| Parameters | |
|---|---|
state: SubcomposeLayoutState |
the state object to be used by the layout. |
modifier: Modifier = Modifier |
|
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult |
Measure policy which provides ability to subcompose during the measuring. |
SubcomposeSlotReusePolicy
fun SubcomposeSlotReusePolicy(maxSlotsToRetainForReuse: Int): SubcomposeSlotReusePolicy
Creates SubcomposeSlotReusePolicy which retains the fixed amount of slots.
| Parameters | |
|---|---|
maxSlotsToRetainForReuse: Int |
the |
lerp
fun lerp(start: ScaleFactor, stop: ScaleFactor, fraction: Float): ScaleFactor
Linearly interpolate between two ScaleFactor parameters
The fraction argument represents position on the timeline, with 0.0 meaning that the interpolation has not started, returning start (or something equivalent to start), 1.0 meaning that the interpolation has finished, returning stop (or something equivalent to stop), and values in between meaning that the interpolation is at the relevant point on the timeline between start and stop. The interpolation can be extrapolated beyond 0.0 and 1.0, so negative values and values greater than 1.0 are valid (and can easily be generated by curves).
Values for fraction are usually obtained from an AnimationAnimationController.
onVisibilityChangedNode
fun onVisibilityChangedNode(
minDurationMs: @IntRange(from = 0) Long = 0,
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f,
viewportBounds: LayoutBoundsHolder? = null,
callback: (Boolean) -> Unit
): DelegatableNode
Creates a DelegatableNode for a modifier node, implementing the contract of onVisibilityChanged Modifier. Such a node could be delegated to as part of a custom modifier node via androidx.compose.ui.node.DelegatingNode.delegate. In most of the cases users should just use onVisibilityChanged Modifier directly.
Note that if you need to update some of the params, it is recommended to androidx.compose.ui.node.DelegatingNode.undelegate the previous node, and delegate again to a new one with the correct values.
Registers a callback to monitor whether or not the node is inside of the viewport of the window or not. Example use cases for this include, auto-playing videos in a feed, logging how long an item was visible, and starting/stopping animations.
| Parameters | |
|---|---|
minDurationMs: @IntRange(from = 0) Long = 0 |
the amount of time in milliseconds that this node should be considered visible before invoking the callback with (true). Depending on your use case, it might be useful to provide a non-zero number here if it is desirable to avoid triggering the callback on elements during really fast scrolls where they went from visible to invisible in a really short amount of time. |
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
the fraction of the node which should be inside the viewport for the callback to get called with a value of true. A value of 1f means that the entire bounds of the rect need to be inside of the viewport, or that the rect fills 100% of the viewport. A value of 0f means that this will get triggered as soon as a non-zero amount of pixels are inside of the viewport. |
viewportBounds: LayoutBoundsHolder? = null |
a reference to the bounds to use as a "viewport" with which to calculate the amount of visibility this element has inside of that viewport. This is most commonly used to account for UI elements such as navigation bars which are drawn on top of the content that this modifier is applied to. It is required that this be passed in to a |
callback: (Boolean) -> Unit |
lambda that is invoked when the fraction of this node inside of the specified viewport crosses the |
| See also | |
|---|---|
onVisibilityChanged |
Extension functions
approachLayout
fun Modifier.approachLayout(
isMeasurementApproachInProgress: (lookaheadSize: IntSize) -> Boolean,
isPlacementApproachInProgress: Placeable.PlacementScope.(lookaheadCoordinates: LayoutCoordinates) -> Boolean = defaultPlacementApproachInProgress,
approachMeasure: ApproachMeasureScope.(measurable: Measurable, constraints: Constraints) -> MeasureResult
): Modifier
Creates an approach layout intended to help gradually approach the destination layout calculated in the lookahead pass. This can be particularly helpful when the destination layout is anticipated to change drastically and would consequently result in visual disruptions.
In order to create a smooth approach, an interpolation (often through animations) can be used in approachMeasure to interpolate the measurement or placement from a previously recorded size and/or position to the destination/target size and/or position. The destination size is available in ApproachMeasureScope as ApproachMeasureScope.lookaheadSize. And the target position can also be acquired in ApproachMeasureScope during placement by using LookaheadScope.localLookaheadPositionOf with the layout's Placeable.PlacementScope.coordinates. The sample code below illustrates how that can be achieved.
isMeasurementApproachInProgress signals whether the measurement is in progress of approaching destination size. It will be queried after the destination has been determined by the lookahead pass, before approachMeasure is invoked. The lookahead size is provided to isMeasurementApproachInProgress for convenience in deciding whether the destination size has been reached.
isMeasurementApproachInProgress indicates whether the position is currently approaching destination defined by the lookahead, hence it's a signal to the system for whether additional approach placements are necessary. isPlacementApproachInProgress will be invoked after the destination position has been determined by lookahead pass, and before the placement phase in approachMeasure.
Once both isMeasurementApproachInProgress and isPlacementApproachInProgress return false, the system may skip approach pass until additional approach passes are necessary as indicated by isMeasurementApproachInProgress and isPlacementApproachInProgress.
IMPORTANT: It is important to be accurate in isPlacementApproachInProgress and isMeasurementApproachInProgress. A prolonged indication of incomplete approach will prevent the system from potentially skipping approach pass when possible.
import androidx.compose.animation.core.AnimationVector2D import androidx.compose.animation.core.DeferredTargetAnimation import androidx.compose.animation.core.VectorConverter import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round // Creates a custom modifier that animates the constraints and measures child with the // animated constraints. This modifier is built on top of `Modifier.approachLayout` to approach // th destination size determined by the lookahead pass. A resize animation will be kicked off // whenever the lookahead size changes, to animate children from current size to destination // size. Fixed constraints created based on the animation value will be used to measure // child, so the child layout gradually changes its animated constraints until the approach // completes. fun Modifier.animateConstraints( sizeAnimation: DeferredTargetAnimation<IntSize, AnimationVector2D>, coroutineScope: CoroutineScope, ) = this.approachLayout( isMeasurementApproachInProgress = { lookaheadSize -> // Update the target of the size animation. sizeAnimation.updateTarget(lookaheadSize, coroutineScope) // Return true if the size animation has pending target change or hasn't finished // running. !sizeAnimation.isIdle } ) { measurable, _ -> // In the measurement approach, the goal is to gradually reach the destination size // (i.e. lookahead size). To achieve that, we use an animation to track the current // size, and animate to the destination size whenever it changes. Once the animation // finishes, the approach is complete. // First, update the target of the animation, and read the current animated size. val (width, height) = sizeAnimation.updateTarget(lookaheadSize, coroutineScope) // Then create fixed size 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) } } var fullWidth by remember { mutableStateOf(false) } // Creates a size animation with a target unknown at the time of instantiation. val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) } val coroutineScope = rememberCoroutineScope() Row( (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp)) .height(200.dp) // Use the custom modifier created above to animate the constraints passed // to the child, and therefore resize children in an animation. .animateConstraints(sizeAnimation, coroutineScope) .clickable { fullWidth = !fullWidth } ) { Box(Modifier.weight(1f).fillMaxHeight().background(Color(0xffff6f69))) Box(Modifier.weight(2f).fillMaxHeight().background(Color(0xffffcc5c))) }
| See also | |
|---|---|
ApproachLayoutModifierNode |
boundsInParent
fun LayoutCoordinates.boundsInParent(): Rect
Returns the bounding box of the child in the parent's content area, including any clipping done with respect to the parent. For the root, the bounds is positioned at (0, 0) and sized to the size of the root.
boundsInRoot
fun LayoutCoordinates.boundsInRoot(): Rect
The boundaries of this layout inside the root composable.
boundsInWindow
fun LayoutCoordinates.boundsInWindow(clipBounds: Boolean = true): Rect
The boundaries of this layout relative to the window's origin.
| Parameters | |
|---|---|
clipBounds: Boolean = true |
Whether to clip the bounds of the layout to the window's boundaries. If |
| Returns | |
|---|---|
Rect |
A |
div
operator fun Size.div(scaleFactor: ScaleFactor): Size
Division operator with Size
Return a new Size with the width and height divided by ScaleFactor.scaleX and ScaleFactor.scaleY respectively
findRootCoordinates
fun LayoutCoordinates.findRootCoordinates(): LayoutCoordinates
Walks up the LayoutCoordinates hierarchy to find the LayoutCoordinates whose LayoutCoordinates.parentCoordinates is null and returns it. If LayoutCoordinates.isAttached, this will have the size of the androidx.compose.ui.platform.ComposeView.
getDisplayCutoutBounds
fun Placeable.PlacementScope.getDisplayCutoutBounds(): List<RectRulers>
Returns a List of RectRulers, one RectRulers for each display cutout. Each RectRulers provides values for the bounds of the display cutout. WindowInsetsRulers.DisplayCutout provides the safe inset values for content avoiding all display cutouts.
innermostOf
fun RectRulers.Companion.innermostOf(vararg rulers: RectRulers): RectRulers
Merges multiple RectRulers into a single RectRulers, using the inner-most value. That is, the RectRulers.left will be the greatest RectRulers.left, the RectRulers.top will be the greatest RectRulers.top, the RectRulers.right will be the least RectRulers.right, and the RectRulers.bottom will be the least of all rulers.
When rulers provide non-overlapping values, the result may have negative size. For example, if one RectRulers provides (10, 20, 30, 40) as their ruler values, and another provides (1, 1, 5, 5), the merged result will be (10, 20, 5, 5).
If one of the rulers does not provide a value, it will not be considered in the calculation.
layout
fun Modifier.layout(measure: MeasureScope.(Measurable, Constraints) -> MeasureResult): Modifier
Creates a LayoutModifier that allows changing how the wrapped element is measured and laid out.
This is a convenience API of creating a custom LayoutModifier modifier, without having to create a class or an object that implements the LayoutModifier interface. The intrinsic measurements follow the default logic provided by the LayoutModifier.
Example usage:
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.offset Box( Modifier.background(Color.Gray).layout { measurable, constraints -> // an example modifier that adds 50 pixels of vertical padding. val padding = 50 val placeable = measurable.measure(constraints.offset(vertical = -padding)) layout(placeable.width, placeable.height + padding) { placeable.placeRelative(0, padding) } } ) { Box(Modifier.fillMaxSize().background(Color.DarkGray)) }
| See also | |
|---|---|
LayoutModifier |
layoutBounds
fun Modifier.layoutBounds(holder: LayoutBoundsHolder): Modifier
This will map the RelativeLayoutBounds of the modifier into the provided LayoutBoundsHolder. A given instance of LayoutBoundsHolder should not be passed into more than one of these modifiers.
layoutId
fun Modifier.layoutId(layoutId: Any): Modifier
Tag the element with layoutId to identify the element within its parent.
Example usage:
import androidx.compose.foundation.layout.Box import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints Layout({ // Here the Containers are only needed to apply the modifiers. You could use the // modifier on header and footer directly if they are composables accepting modifiers. Box(Modifier.layoutId("header")) { header() } Box(Modifier.layoutId("footer")) { footer() } }) { measurables, constraints -> val placeables = measurables.map { measurable -> when (measurable.layoutId) { // You should use appropriate constraints. Here we measure fake constraints. "header" -> measurable.measure(Constraints.fixed(100, 100)) "footer" -> measurable.measure(constraints) else -> error("Unexpected tag") } } // Size should be derived from children measured sizes on placeables, // but this is simplified for the purposes of the example. layout(100, 100) { placeables.forEach { it.placeRelative(0, 0) } } }
lookaheadScopeCoordinates
fun LookaheadScope.lookaheadScopeCoordinates(
sourceCoordinates: LayoutCoordinates
): LayoutCoordinates
Obtains the LayoutCoordinates for the given LookaheadScope using a LayoutCoordinates within the LookaheadScope.
Important: This must be an actual LayoutCoordinates instance from the PlacementScope or Modifier APIs. The Layout that associates with the coordinates needs to be within the subtree of the LookaheadScope. Using a custom LayoutCoordinates implementation will result in an IllegalArgumentException.
| Parameters | |
|---|---|
sourceCoordinates: LayoutCoordinates |
A |
onFirstVisible
fun Modifier.onFirstVisible(
minDurationMs: @IntRange(from = 0) Long = 0,
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f,
viewportBounds: LayoutBoundsHolder? = null,
callback: () -> Unit
): Modifier
Registers a callback to monitor when the node is first inside of the viewport of the window or not. Example use cases for this include impression and view counting, starting animations, or doing work that is only required once the item is visible to the user.
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onFirstVisible @Composable fun VideoFeed(feedData: List<Video>, logger: Logger) { LazyColumn { items(feedData) { video -> VideoRow( video, Modifier.onFirstVisible(minDurationMs = 500, minFractionVisible = 1f) { logger.logImpression(video.id) }, ) } } }
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.LayoutBoundsHolder import androidx.compose.ui.layout.layoutBounds import androidx.compose.ui.layout.onFirstVisible @Composable fun VideoFeed(feedData: List<Video>, logger: Logger) { val viewport = remember { LayoutBoundsHolder() } LazyColumn(Modifier.layoutBounds(viewport)) { items(feedData) { video -> VideoRow( video, Modifier.onFirstVisible( minDurationMs = 500, minFractionVisible = 1f, viewportBounds = viewport, ) { logger.logImpression(video.id) }, ) } } }
| Parameters | |
|---|---|
minDurationMs: @IntRange(from = 0) Long = 0 |
the amount of time in milliseconds that this node should be considered visible before invoking the callback. Depending on your use case, it might be useful to provide a non-zero number here if it is desirable to avoid triggering the callback on elements during really fast scrolls where they are only visible for a short amount of time. |
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
the fraction of the node which should be inside the viewport to be considered visible. A value of 1f means that the entire bounds of the rect need to be inside of the viewport, or that the rect fills 100% of the viewport. A value of 0f means that this will get triggered as soon as a non-zero amount of pixels are inside of the viewport. |
viewportBounds: LayoutBoundsHolder? = null |
a reference to the bounds to use as a "viewport" with which to calculate the amount of visibility this element has inside of that viewport. This is most commonly used to account for UI elements such as navigation bars which are drawn on top of the content that this modifier is applied to. It is required that this be passed in to a |
callback: () -> Unit |
lambda that is invoked when the fraction of this node inside of the specified viewport is greater than minFractionVisible. This lambda will only get invoked a maximum of one time while the element is attached. |
onGloballyPositioned
fun Modifier.onGloballyPositioned(
onGloballyPositioned: (LayoutCoordinates) -> Unit
): Modifier
Invoke onGloballyPositioned with the LayoutCoordinates of the element when the global position of the content may have changed. Note that it will be called after a composition when the coordinates are finalized.
This callback will be invoked at least once when the LayoutCoordinates are available, and every time the element's position changes within the window. However, it is not guaranteed to be invoked every time the position relative to the screen of the modified element changes. For example, the system may move the contents inside a window around without firing a callback. If you are using the LayoutCoordinates to calculate position on the screen, and not just inside the window, you may not receive a callback.
Usage example:
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.unit.dp Column( Modifier.onGloballyPositioned { coordinates -> // This will be the size of the Column. coordinates.size // The position of the Column relative to the application window. coordinates.positionInWindow() // The position of the Column relative to the Compose root. coordinates.positionInRoot() // These will be the alignment lines provided to the layout (empty here for Column). coordinates.providedAlignmentLines // This will be a LayoutCoordinates instance corresponding to the parent of Column. coordinates.parentLayoutCoordinates } ) { Box(Modifier.size(20.dp).background(Color.Green)) Box(Modifier.size(20.dp).background(Color.Blue)) }
onLayoutRectChanged
fun Modifier.onLayoutRectChanged(
throttleMillis: Long = 0,
debounceMillis: Long = 64,
callback: (RelativeLayoutBounds) -> Unit
): Modifier
Invokes callback with the position of this layout node relative to the coordinate system of the root of the composition, as well as in screen coordinates and window coordinates. This will be called after layout pass. This API allows for throttling and debouncing parameters in order to moderate the frequency with which the callback gets invoked during high rates of change (e.g. scrolling).
Specifying throttleMillis will prevent callback from being executed more than once over that time period. Specifying debounceMillis will delay the execution of callback until that amount of time has elapsed without a new position, scheduling the callback to be executed when that amount of time expires.
Specifying 0 for both throttleMillis and debounceMillis will result in the callback being executed every time the position has changed. Specifying non-zero amounts for both will result in both conditions being met. Specifying a non-zero throttleMillis but a zero debounceMillis is equivalent to providing the same value for both throttleMillis and debounceMillis.
| Parameters | |
|---|---|
throttleMillis: Long = 0 |
The duration, in milliseconds, to prevent |
debounceMillis: Long = 64 |
The duration, in milliseconds, to delay the execution of |
callback: (RelativeLayoutBounds) -> Unit |
The callback to be executed. |
onPlaced
fun Modifier.onPlaced(onPlaced: (LayoutCoordinates) -> Unit): Modifier
Invoke onPlaced after the parent LayoutModifier and parent layout has been placed and before child LayoutModifier is placed. This allows child LayoutModifier to adjust its own placement based on where the parent is.
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector2D import androidx.compose.animation.core.Spring.StiffnessMediumLow import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.spring import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round fun Modifier.animatePlacement(): Modifier = composed { val scope = rememberCoroutineScope() var targetOffset by remember { mutableStateOf(IntOffset.Zero) } var animatable by remember { mutableStateOf<Animatable<IntOffset, AnimationVector2D>?>(null) } this.onPlaced { // Calculate the position in the parent layout targetOffset = it.positionInParent().round() } .offset { // Animate to the new target offset when alignment changes. val anim = animatable ?: Animatable(targetOffset, IntOffset.VectorConverter).also { animatable = it } if (anim.targetValue != targetOffset) { scope.launch { anim.animateTo(targetOffset, spring(stiffness = StiffnessMediumLow)) } } // Offset the child in the opposite direction to the targetOffset, and slowly catch // up to zero offset via an animation to achieve an overall animated movement. animatable?.let { it.value - targetOffset } ?: IntOffset.Zero } } @Composable fun AnimatedChildAlignment(alignment: Alignment) { Box(Modifier.fillMaxSize().padding(4.dp).border(1.dp, Color.Red)) { Box( modifier = Modifier.animatePlacement().align(alignment).size(100.dp).background(Color.Red) ) } }
onSizeChanged
fun Modifier.onSizeChanged(onSizeChanged: (IntSize) -> Unit): Modifier
Invoked with the size of the modified Compose UI element when the element is first measured or when the size of the element changes.
There are no guarantees onSizeChanged will not be re-invoked with the same size.
Using the onSizeChanged size value in a MutableState to update layout causes the new size value to be read and the layout to be recomposed in the succeeding frame, resulting in a one frame lag.
You can use onSizeChanged to affect drawing operations. Use Layout or SubcomposeLayout to enable the size of one component to affect the size of another.
Example usage:
import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged // Use onSizeChanged() for diagnostics. Use Layout or SubcomposeLayout if you want // to use the size of one component to affect the size of another component. Text( "Hello $name", Modifier.onSizeChanged { size -> println("The size of the Text in pixels is $size") }, )
onVisibilityChanged
fun Modifier.onVisibilityChanged(
minDurationMs: @IntRange(from = 0) Long = 0,
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f,
viewportBounds: LayoutBoundsHolder? = null,
callback: (Boolean) -> Unit
): Modifier
Registers a callback to monitor whether or not the node is inside of the viewport of the window or not. Example use cases for this include, auto-playing videos in a feed, logging how long an item was visible, and starting/stopping animations.
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onVisibilityChanged @Composable fun VideoFeed(feedData: List<Video>) { LazyColumn { items(feedData) { video -> VideoRow( video, Modifier.onVisibilityChanged(minDurationMs = 500, minFractionVisible = 1f) { visible -> if (visible) video.play() else video.pause() }, ) } } }
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.LayoutBoundsHolder import androidx.compose.ui.layout.layoutBounds import androidx.compose.ui.layout.onVisibilityChanged @Composable fun VideoFeed(feedData: List<Video>) { val viewport = remember { LayoutBoundsHolder() } LazyColumn(Modifier.layoutBounds(viewport)) { items(feedData) { video -> VideoRow( video, Modifier.onVisibilityChanged( minDurationMs = 500, minFractionVisible = 1f, viewportBounds = viewport, ) { visible -> if (visible) video.play() else video.pause() }, ) } } }
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onVisibilityChanged @Composable fun VideoFeed(feedData: List<Video>, logger: Logger) { LazyColumn { items(feedData) { video -> var startTime by remember { mutableLongStateOf(-1) } VideoRow( video, Modifier.onVisibilityChanged(minDurationMs = 500, minFractionVisible = 1f) { visible -> if (visible) { startTime = System.currentTimeMillis() } else if (startTime >= 0) { val durationMs = System.currentTimeMillis() - startTime logger.logImpression(video.id, durationMs) startTime = -1 } }, ) } } }
| Parameters | |
|---|---|
minDurationMs: @IntRange(from = 0) Long = 0 |
the amount of time in milliseconds that this node should be considered visible before invoking the callback with (true). Depending on your use case, it might be useful to provide a non-zero number here if it is desirable to avoid triggering the callback on elements during really fast scrolls where they went from visible to invisible in a really short amount of time. |
minFractionVisible: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
the fraction of the node which should be inside the viewport for the callback to get called with a value of true. A value of 1f means that the entire bounds of the rect need to be inside of the viewport, or that the rect fills 100% of the viewport. A value of 0f means that this will get triggered as soon as a non-zero amount of pixels are inside of the viewport. |
viewportBounds: LayoutBoundsHolder? = null |
a reference to the bounds to use as a "viewport" with which to calculate the amount of visibility this element has inside of that viewport. This is most commonly used to account for UI elements such as navigation bars which are drawn on top of the content that this modifier is applied to. It is required that this be passed in to a |
callback: (Boolean) -> Unit |
lambda that is invoked when the fraction of this node inside of the specified viewport crosses the |
outermostOf
fun RectRulers.Companion.outermostOf(vararg rulers: RectRulers): RectRulers
Merges multiple RectRulers into a single RectRulers, using the outer-most value. That is, the RectRulers.left will be the least RectRulers.left, the RectRulers.top will be the least RectRulers.top, the RectRulers.right will be the greatest RectRulers.right, and the RectRulers.bottom will be the greatest of all rulers.
If one of the rulers does not provide a value, it will not be considered in the calculation.
positionInParent
fun LayoutCoordinates.positionInParent(): Offset
Returns the position of the top-left in the parent's content area or (0, 0) for the root.
positionInRoot
fun LayoutCoordinates.positionInRoot(): Offset
The position of this layout inside the root composable.
positionInWindow
fun LayoutCoordinates.positionInWindow(): Offset
The position of this layout relative to the window.
positionOnScreen
fun LayoutCoordinates.positionOnScreen(): Offset
The position of this layout on the device's screen. Returns Offset.Unspecified if the conversion cannot be performed.
registerOnGlobalLayoutListener
fun DelegatableNode.registerOnGlobalLayoutListener(
throttleMillis: Long,
debounceMillis: Long,
callback: (RelativeLayoutBounds) -> Unit
): DelegatableNode.RegistrationHandle
Registers a callback to be executed with the position of this modifier node relative to the coordinate system of the root of the composition, as well as in screen coordinates and window coordinates, see RelativeLayoutBounds.
It may also be used to calculate certain Layout relationships at the time of the callback execution, such as RelativeLayoutBounds.calculateOcclusions.
This will be called after layout pass. This API allows for throttling and debouncing parameters in order to moderate the frequency with which the callback gets invoked during high rates of change (e.g. scrolling).
Specifying throttleMillis will prevent callback from being executed more than once over that time period. Specifying debounceMillis will delay the execution of callback until that amount of time has elapsed without a new position.
Specifying 0 for both throttleMillis and debounceMillis will result in the callback being executed every time the position has changed. Specifying non-zero amounts for both will result in both conditions being met.
| Parameters | |
|---|---|
throttleMillis: Long |
The duration, in milliseconds, to prevent |
debounceMillis: Long |
The duration, in milliseconds, to delay the execution of |
callback: (RelativeLayoutBounds) -> Unit |
The callback to be executed, provides a new |
| Returns | |
|---|---|
DelegatableNode.RegistrationHandle |
an object which should be used to unregister/dispose this callback, such as when a node is detached |
registerOnLayoutRectChanged
fun DelegatableNode.registerOnLayoutRectChanged(
throttleMillis: Long,
debounceMillis: Long,
callback: (RelativeLayoutBounds) -> Unit
): DelegatableNode.RegistrationHandle
Registers a callback to be executed with the position of this modifier node relative to the coordinate system of the root of the composition, as well as in screen coordinates and window coordinates. This will be called after layout pass. This API allows for throttling and debouncing parameters in order to moderate the frequency with which the callback gets invoked during high rates of change (e.g. scrolling).
Specifying throttleMillis will prevent callback from being executed more than once over that time period. Specifying debounceMillis will delay the execution of callback until that amount of time has elapsed without a new position.
Specifying 0 for both throttleMillis and debounceMillis will result in the callback being executed every time the position has changed. Specifying non-zero amounts for both will result in both conditions being met.
| Parameters | |
|---|---|
throttleMillis: Long |
The duration, in milliseconds, to prevent |
debounceMillis: Long |
The duration, in milliseconds, to delay the execution of |
callback: (RelativeLayoutBounds) -> Unit |
The callback to be executed. |
| Returns | |
|---|---|
DelegatableNode.RegistrationHandle |
an object which should be used to unregister/dispose this callback |
| See also | |
|---|---|
onLayoutRectChanged |
takeOrElse
inline fun ScaleFactor.takeOrElse(block: () -> ScaleFactor): ScaleFactor
If this ScaleFactor then this is returned, otherwise block is executed and its result is returned.
times
operator fun Size.times(scaleFactor: ScaleFactor): Size
Multiplication operator with Size.
Return a new Size with the width and height multiplied by the ScaleFactor.scaleX and ScaleFactor.scaleY respectively
times
operator fun ScaleFactor.times(size: Size): Size
Multiplication operator with Size with reverse parameter types to maintain commutative properties of multiplication
Return a new Size with the width and height multiplied by the ScaleFactor.scaleX and ScaleFactor.scaleY respectively
Top-level properties
FirstBaseline
val FirstBaseline: HorizontalAlignmentLine
AlignmentLine defined by the baseline of a first line of a androidx.compose.foundation.text.BasicText
LastBaseline
val LastBaseline: HorizontalAlignmentLine
AlignmentLine defined by the baseline of the last line of a androidx.compose.foundation.text.BasicText
LocalPinnableContainer
val LocalPinnableContainer: ProvidableCompositionLocal<PinnableContainer?>
Use this composition local to get the PinnableContainer handling the current subhierarchy.
It will be not null, for example, when the current content is composed as an item of lazy list.
ModifierLocalBeyondBoundsLayout
val ModifierLocalBeyondBoundsLayout: ProvidableModifierLocal<BeyondBoundsLayout?>
A modifier local that provides access to a BeyondBoundsLayout that a child can use to ask a parent to layout more items that are beyond its visible bounds.
Extension properties
layoutId
val Measurable.layoutId: Any?
Retrieves the tag associated to a composable with the Modifier.layoutId modifier. For a parent data value to be returned by this property when not using the Modifier.layoutId modifier, the parent data value should implement the LayoutIdParentData interface.
Example usage:
import androidx.compose.foundation.layout.Box import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints Layout({ // Here the Containers are only needed to apply the modifiers. You could use the // modifier on header and footer directly if they are composables accepting modifiers. Box(Modifier.layoutId("header")) { header() } Box(Modifier.layoutId("footer")) { footer() } }) { measurables, constraints -> val placeables = measurables.map { measurable -> when (measurable.layoutId) { // You should use appropriate constraints. Here we measure fake constraints. "header" -> measurable.measure(Constraints.fixed(100, 100)) "footer" -> measurable.measure(constraints) else -> error("Unexpected tag") } } // Size should be derived from children measured sizes on placeables, // but this is simplified for the purposes of the example. layout(100, 100) { placeables.forEach { it.placeRelative(0, 0) } } }