DelegatableNode

Known direct subclasses
CompositionLocalConsumerModifierNode

Implementing this interface allows your Modifier.Node subclass to read CompositionLocals via the currentValueOf function.

DragAndDropModifierNode

This interface is deprecated. This interface is deprecated in favor to DragAndDropSourceModifierNode and DragAndDropTargetModifierNode

DrawModifierNode

A Modifier.Node that draws into the space of the layout.

FocusEventModifierNode

Implement this interface create a modifier node that can be used to observe focus state changes to a FocusTargetNode down the hierarchy.

FocusPropertiesModifierNode

Implement this interface create a modifier node that can be used to modify the focus properties of the associated FocusTargetNode.

FocusRequesterModifierNode

Implement this interface to create a modifier node that can be used to request changes in the focus state of a FocusTargetNode down the hierarchy.

FocusTargetModifierNode

This modifier node can be delegated to in order to create a modifier that makes a component focusable.

GlobalPositionAwareModifierNode

A androidx.compose.ui.Modifier.Node whose onGloballyPositioned is called with the final LayoutCoordinates of the Layout when the global position of the content may have changed.

KeyInputModifierNode

Implement this interface to create a Modifier.Node that can intercept hardware Key events.

LayoutAwareModifierNode

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

LayoutModifierNode

A Modifier.Node that changes how its wrapped content is measured and laid out.

Modifier.Node

The longer-lived object that is created for each Modifier.Element applied to a androidx.compose.ui.layout.Layout.

ModifierLocalModifierNode

A androidx.compose.ui.Modifier.Node that is capable of consuming and providing ModifierLocal values.

ObserverModifierNode

Modifier.Nodes that implement ObserverNode can provide their own implementation of onObservedReadsChanged that will be called in response to changes to snapshot objects read within an observeReads block.

ParentDataModifierNode

A Modifier.Node that provides data to the parent Layout.

PlatformTextInputModifierNode

A modifier node that can connect to the platform's text input IME system.

PointerInputModifierNode

A androidx.compose.ui.Modifier.Node that receives PointerInputChanges, interprets them, and consumes the aspects of the changes that it is react to such that other PointerInputModifierNodes don't also react to them.

RotaryInputModifierNode

Implement this interface to create a Modifier.Node that can intercept rotary scroll events.

SemanticsModifierNode

A Modifier.Node that adds semantics key/value for use in testing, accessibility, and similar use cases.

SoftKeyboardInterceptionModifierNode

Implement this interface to create a Modifier.Node that can intercept hardware Key events before they are sent to the software keyboard.

TraversableNode

Allows Modifier.Node classes to traverse up/down the Node tree for classes of the same type or for a particular key (traverseKey).

Known indirect subclasses
ApproachLayoutModifierNode

ApproachLayoutModifierNode is designed to support gradually approaching the destination layout calculated in the lookahead pass.

CacheDrawModifierNode

Expands on the androidx.compose.ui.node.DrawModifierNode by adding the ability to invalidate the draw cache for changes in things like shapes and bitmaps (see Modifier.border for a usage examples).

DelegatingNode

A Modifier.Node which is able to delegate work to other Modifier.Node instances.

DragAndDropSourceModifierNode

A Modifier.Node that can be used as a source for platform drag and drop operations.

DragAndDropTargetModifierNode

A Modifier.Node that can be used as a target for platform drag and drop operations.

SuspendingPointerInputModifierNode

Extends PointerInputModifierNode with a handler to execute asynchronously when an event occurs and a function to reset that handler (cancels the existing coroutine and essentially resets the handler's execution).


Represents a Modifier.Node which can be a delegate of another Modifier.Node. Since Modifier.Node implements this interface, in practice any Modifier.Node can be delegated.

Summary

Public functions

open Unit

Invoked when the density changes for this node.

Cmn
open Unit

Invoked when the layout direction changes for this node.

Cmn

Public properties

Modifier.Node

A reference of the Modifier.Node that holds this node's position in the node hierarchy.

Cmn

Extension functions

Unit

Invalidates the subtree of this layout, including layout, drawing, parent data, etc.

Cmn
Density

Returns the current Density of the LayoutNode that this DelegatableNode is attached to.

Cmn
GraphicsContext

Returns the current GraphicsContext of the Owner

Cmn
LayoutCoordinates

Returns the LayoutCoordinates of this node.

Cmn
LayoutDirection

Returns the current LayoutDirection of the LayoutNode that this DelegatableNode is attached to.

Cmn
View

The Android View hosting the composition.

android
DisposableHandle
DelegatableNode.registerOnRectChanged(
    throttleMs: Int,
    debounceMs: Int,
    callback: (RectInfo) -> Unit
)

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.

Cmn
suspend Unit

Bring this node into bounds by making all the scrollable parents scroll appropriately.

Cmn
TraversableNode?

Finds the nearest traversable ancestor with a matching key.

Cmn
Unit
DelegatableNode.traverseAncestors(
    key: Any?,
    block: (TraversableNode) -> Boolean
)

Executes block for all ancestors with a matching key.

Cmn
Unit
DelegatableNode.traverseChildren(
    key: Any?,
    block: (TraversableNode) -> Boolean
)

Executes block for all direct children of the node with a matching key.

Cmn
Unit

Conditionally executes block for each descendant with a matching key.

Cmn

Public functions

onDensityChange

open fun onDensityChange(): Unit

Invoked when the density changes for this node. This affects Dp to pixel conversions, and can cause coordinates / other values to change.

Changes to density will automatically invalidate layout / draw modifiers, as layout, measurement, and draw depend on density. This callback can be used to update any other node state that depends on density, outside of these phases. Density can be retrieved inside a node by using androidx.compose.ui.node.requireDensity.

onLayoutDirectionChange

open fun onLayoutDirectionChange(): Unit

Invoked when the layout direction changes for this node. This can affect the layout and drawing of nodes.

Changes to layout direction will automatically invalidate layout / draw modifiers, as layout, measurement, and draw depend on layout direction. This callback can be used to update any other node state that depends on layout direction, outside of these phases. Layout direction can be retrieved inside a node by using androidx.compose.ui.node.requireLayoutDirection.

Public properties

node

val nodeModifier.Node

A reference of the Modifier.Node that holds this node's position in the node hierarchy. If the node is a delegate of another node, this will point to the root delegating node that is actually part of the node tree. Otherwise, this will point to itself.

Extension functions

invalidateSubtree

fun DelegatableNode.invalidateSubtree(): Unit

Invalidates the subtree of this layout, including layout, drawing, parent data, etc.

Calling this method can be a relatively expensive operation as it will cause the entire subtree to relayout and redraw instead of just parts that are otherwise invalidated. Its use should be limited to structural changes.

requireDensity

fun DelegatableNode.requireDensity(): Density

Returns the current Density of the LayoutNode that this DelegatableNode is attached to. If the node is not attached, this function will throw an IllegalStateException.

requireGraphicsContext

fun DelegatableNode.requireGraphicsContext(): GraphicsContext

Returns the current GraphicsContext of the Owner

requireLayoutCoordinates

fun DelegatableNode.requireLayoutCoordinates(): LayoutCoordinates

Returns the LayoutCoordinates of this node.

To get a signal when the LayoutCoordinates become available, or when its parent places it, implement LayoutAwareModifierNode.

Throws
kotlin.IllegalStateException

When either this node is not attached, or the LayoutCoordinates object is not attached.

requireLayoutDirection

fun DelegatableNode.requireLayoutDirection(): LayoutDirection

Returns the current LayoutDirection of the LayoutNode that this DelegatableNode is attached to. If the node is not attached, this function will throw an IllegalStateException.

requireView

fun DelegatableNode.requireView(): View

The Android View hosting the composition.

Throws
kotlin.IllegalStateException

If the modifier node is not attached.

registerOnRectChanged

fun DelegatableNode.registerOnRectChanged(
    throttleMs: Int,
    debounceMs: Int,
    callback: (RectInfo) -> Unit
): DisposableHandle

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 throttleMs will prevent callback from being executed more than once over that time period. Specifying debounceMs will delay the execution of callback until that amount of time has elapsed without a new position.

Specifying 0 for both throttleMs and debounceMs 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
throttleMs: Int

The duration, in milliseconds, to prevent callback from being executed more than once over that time period.

debounceMs: Int

The duration, in milliseconds, to delay the execution of callback until that amount of time has elapsed without a new position.

callback: (RectInfo) -> Unit

The callback to be executed.

Returns
DisposableHandle

an object which should be used to unregister/dispose this callback

See also
onRectChanged
suspend fun DelegatableNode.scrollIntoView(rect: Rect? = null): Unit

Bring this node into bounds by making all the scrollable parents scroll appropriately.

This method will not return until this request is satisfied or a newer request interrupts it. If this call is interrupted by a newer call, this method will throw a CancellationException.

import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusTarget
import androidx.compose.ui.focus.onFocusChanged

Row(Modifier.horizontalScroll(rememberScrollState())) {
    repeat(100) {
        val bringIntoViewRequester = remember { BringIntoViewRequester() }
        val coroutineScope = rememberCoroutineScope()
        Box(
            Modifier
                // This associates the RelocationRequester with a Composable that wants to be
                // brought into view.
                .bringIntoViewRequester(bringIntoViewRequester)
                .onFocusChanged {
                    if (it.isFocused) {
                        coroutineScope.launch {
                            // This sends a request to all parents that asks them to scroll so
                            // that this item is brought into view.
                            bringIntoViewRequester.bringIntoView()
                        }
                    }
                }
                .focusTarget()
        )
    }
}
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp

with(LocalDensity.current) {
    val bringIntoViewRequester = remember { BringIntoViewRequester() }
    val coroutineScope = rememberCoroutineScope()
    Column {
        Box(
            Modifier.border(2.dp, Color.Black)
                .size(500f.toDp())
                .horizontalScroll(rememberScrollState())
        ) {
            Canvas(
                Modifier.size(1500f.toDp(), 500f.toDp())
                    // This associates the RelocationRequester with a Composable that wants
                    // to be brought into view.
                    .bringIntoViewRequester(bringIntoViewRequester)
            ) {
                drawCircle(color = Color.Red, radius = 250f, center = Offset(750f, 250f))
            }
        }
        Button(
            onClick = {
                val circleCoordinates = Rect(500f, 0f, 1000f, 500f)
                coroutineScope.launch {
                    // This sends a request to all parents that asks them to scroll so that
                    // the circle is brought into view.
                    bringIntoViewRequester.bringIntoView(circleCoordinates)
                }
            }
        ) {
            Text("Bring circle into View")
        }
    }
}
Parameters
rect: Rect? = null

The rectangle (In local coordinates) that should be brought into view. If you don't specify the coordinates, the coordinates of the Modifier.bringIntoViewRequester() associated with this BringIntoViewRequester will be used.

findNearestAncestor

fun DelegatableNode.findNearestAncestor(key: Any?): TraversableNode?

Finds the nearest traversable ancestor with a matching key.

traverseAncestors

fun DelegatableNode.traverseAncestors(
    key: Any?,
    block: (TraversableNode) -> Boolean
): Unit

Executes block for all ancestors with a matching key.

Note: The parameter block's return boolean value will determine if the traversal will continue (true = continue, false = cancel).

import androidx.compose.ui.Modifier
import androidx.compose.ui.node.traverseAncestors

val customTraversableModifierNode = CustomTraversableModifierNode()

with(customTraversableModifierNode) {
    traverseAncestors(traverseKey) {
        if (it is CustomTraversableModifierNode) {
            it.doSomethingWithAncestor()
        }
        // Return true to continue searching the tree after a match. If you were looking to
        // match only some of the nodes, you could return false and stop executing the search.
        true
    }
}

traverseChildren

fun DelegatableNode.traverseChildren(
    key: Any?,
    block: (TraversableNode) -> Boolean
): Unit

Executes block for all direct children of the node with a matching key.

Note 1: This stops at the children and does not include grandchildren and so on down the tree.

Note 2: The parameter block's return boolean value will determine if the traversal will continue (true = continue, false = cancel).

import androidx.compose.ui.Modifier
import androidx.compose.ui.node.traverseChildren

val customTraversableModifierNode = CustomTraversableModifierNode()

with(customTraversableModifierNode) {
    traverseChildren(traverseKey) {
        if (it is CustomTraversableModifierNode) {
            it.doSomethingWithChild()
        }
        // Return true to continue searching the tree after a match. If you were looking to
        // match only some of the nodes, you could return false and stop executing the search.
        true
    }
}

traverseDescendants

fun DelegatableNode.traverseDescendants(
    key: Any?,
    block: (TraversableNode) -> TraversableNode.Companion.TraverseDescendantsAction
): Unit

Conditionally executes block for each descendant with a matching key.

Note 1: For nodes that do not have the same key, it will continue to execute the block for descendants below that non-matching node (where there may be a node that matches).

Note 2: The parameter block's return value TraverseDescendantsAction will determine the next step in the traversal.

import androidx.compose.ui.Modifier
import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction
import androidx.compose.ui.node.traverseDescendants

val customTraversableModifierNode = CustomTraversableModifierNode()

with(customTraversableModifierNode) {
    traverseDescendants(traverseKey) {
        if (it is CustomTraversableModifierNode) {
            it.doSomethingWithDescendant()
        }

        // [traverseDescendants()] actually has three options:
        // - ContinueTraversal
        // - SkipSubtreeAndContinueTraversal - rarely used
        // - CancelTraversal
        // They are pretty self explanatory. Usually, you just want to continue or cancel the
        // search. In some rare cases, you might want to skip the subtree but continue searching
        // the tree.
        TraverseDescendantsAction.ContinueTraversal
    }
}