LayoutAwareModifierNode

Interface

Common
interface LayoutAwareModifierNode : DelegatableNode

A androidx.compose.ui.Modifier.Node which receives various callbacks in response to local changes in layout.

This is the androidx.compose.ui.Modifier.Node equivalent of androidx.compose.ui.layout.OnRemeasuredModifier and androidx.compose.ui.layout.OnPlacedModifier

Functions

fun onPlaced(coordinates: LayoutCoordinates)

onPlaced is called 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.

If you only need to access the current LayoutCoordinates at a single point in time from outside this method, use currentLayoutCoordinates.

fun onRemeasured(size: IntSize)

This method is called when the layout content is remeasured. The most common usage is onSizeChanged.

Code Examples

OnSizeChangedSample

@Composable
fun OnSizeChangedSample(name: String) {
    // 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") },
    )
}

OnPlaced

@Composable
fun OnPlaced() {
    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)
            )
        }
    }
}

LayoutAwareModifierNodeSample

@Composable
fun LayoutAwareModifierNodeSample() {
    class SizeLoggerNode(var id: String) : LayoutAwareModifierNode, Modifier.Node() {
        override fun onRemeasured(size: IntSize) {
            println("The size of $id was $size")
        }
    }
    data class LogSizeElement(val id: String) : ModifierNodeElement<SizeLoggerNode>() {
        override fun create(): SizeLoggerNode = SizeLoggerNode(id)
        override fun update(node: SizeLoggerNode) {
            node.id = id
        }
        override fun InspectorInfo.inspectableProperties() {
            name = "logSize"
            properties["id"] = id
        }
    }
    fun Modifier.logSize(id: String) = this then LogSizeElement(id)
}