DelegatingNode


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

This can be useful to compose multiple node implementations into one.

import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.SemanticsModifierNode
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.IntSize

class TapGestureNode(var onTap: () -> Unit) : PointerInputModifierNode, Modifier.Node() {
    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        // ...
    }

    override fun onCancelPointerInput() {
        // ...
    }
}
class TapGestureWithClickSemantics(onTap: () -> Unit) :
    PointerInputModifierNode, SemanticsModifierNode, DelegatingNode() {
    var onTap: () -> Unit
        get() = gesture.onTap
        set(value) {
            gesture.onTap = value
        }

    val gesture = delegate(TapGestureNode(onTap))

    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        gesture.onPointerEvent(pointerEvent, pass, bounds)
    }

    override fun onCancelPointerInput() {
        gesture.onCancelPointerInput()
    }

    override fun SemanticsPropertyReceiver.applySemantics() {
        onClick {
            gesture.onTap()
            true
        }
    }
}
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.SemanticsModifierNode
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.IntSize

class TapGestureNode(var onTap: () -> Unit) : PointerInputModifierNode, Modifier.Node() {
    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        // ...
    }

    override fun onCancelPointerInput() {
        // ...
    }
}

class TapSemanticsNode(var onTap: () -> Unit) : SemanticsModifierNode, Modifier.Node() {
    override fun SemanticsPropertyReceiver.applySemantics() {
        onClick {
            onTap()
            true
        }
    }
}
class TapGestureWithClickSemantics(onTap: () -> Unit) : DelegatingNode() {
    var onTap: () -> Unit
        get() = gesture.onTap
        set(value) {
            gesture.onTap = value
            semantics.onTap = value
        }

    val gesture = delegate(TapGestureNode(onTap))
    val semantics = delegate(TapSemanticsNode(onTap))
}
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.SemanticsModifierNode
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.IntSize

class ExpensivePositionHandlingOnPointerEvents : PointerInputModifierNode, DelegatingNode() {

    val globalAwareNode =
        object : GlobalPositionAwareModifierNode, Modifier.Node() {
            override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
                // ...
            }
        }

    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        // wait until first pointer event to start listening to global
        // position
        if (!globalAwareNode.isAttached) {
            delegate(globalAwareNode)
        }
        // normal input processing
    }

    override fun onCancelPointerInput() {
        // ...
    }
}

class TapGestureNode(var onTap: () -> Unit) : PointerInputModifierNode, Modifier.Node() {
    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) {
        // ...
    }

    override fun onCancelPointerInput() {
        // ...
    }
}

class TapSemanticsNode(var onTap: () -> Unit) : SemanticsModifierNode, Modifier.Node() {
    override fun SemanticsPropertyReceiver.applySemantics() {
        onClick {
            onTap()
            true
        }
    }
}
class TapGestureWithClickSemantics(onTap: () -> Unit) : DelegatingNode() {
    var onTap: () -> Unit
        get() = gesture.onTap
        set(value) {
            gesture.onTap = value
            semantics.onTap = value
        }

    val gesture = delegate(TapGestureNode(onTap))
    val semantics = delegate(TapSemanticsNode(onTap))
}
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode

class MyModifierNode(global: Boolean) : DelegatingNode() {
    val globalAwareNode =
        object : GlobalPositionAwareModifierNode, Modifier.Node() {
                override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
                    // ...
                }
            }
            .also { if (global) delegate(it) }
    var global: Boolean = global
        set(value) {
            if (global && !value) {
                undelegate(globalAwareNode)
            } else if (!global && value) {
                delegate(globalAwareNode)
            }
            field = value
        }
}
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.requireLayoutDirection
import androidx.compose.ui.unit.LayoutDirection

class MyModifierNode : DelegatingNode() {
    val globalAwareNode =
        object : GlobalPositionAwareModifierNode, Modifier.Node() {
            override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
                // ...
            }
        }

    override fun onAttach() {
        // one can conditionally delegate in attach, for instance if certain conditions are met
        if (requireLayoutDirection() == LayoutDirection.Rtl) {
            delegate(globalAwareNode)
        }
    }
}
See also
DelegatingNode

Summary

Public constructors

Cmn

Protected functions

T
<T : DelegatableNode> delegate(delegatableNode: T)

In order to properly delegate work to another Modifier.Node, the delegated instance must be created and returned inside of a delegate call.

Cmn
Unit

This function expects a node which was passed in to delegate for this node, and is currently being delegated to to be passed in as instance.

Cmn

Inherited functions

From androidx.compose.ui.node.DelegatableNode
open Unit

Invoked when the density changes for this node.

Cmn
open Unit

Invoked when the layout direction changes for this node.

Cmn
From androidx.compose.ui.Modifier.Node
open Unit

Called when the node is attached to a androidx.compose.ui.layout.Layout which is part of the UI tree.

Cmn
open Unit

Called when the node is not attached to a androidx.compose.ui.layout.Layout which is not a part of the UI tree anymore.

Cmn
open Unit

Called when the node is about to be moved to a pool of layouts ready to be reused.

Cmn
Unit
sideEffect(effect: () -> Unit)

This can be called to register effect as a function to be executed after all of the changes to the tree are applied.

Cmn

Inherited properties

From androidx.compose.ui.Modifier.Node
CoroutineScope

A CoroutineScope that can be used to launch tasks that should run while the node is attached.

Cmn
Boolean

Indicates that the node is attached to a androidx.compose.ui.layout.Layout which is part of the UI tree.

Cmn
final Modifier.Node

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

Cmn
open Boolean

If this property returns true, then nodes will be automatically invalidated after the modifier update completes (For example, if the returned Node is a DrawModifierNode, its DrawModifierNode.invalidateDraw function will be invoked automatically as part of auto invalidation).

Cmn

Public constructors

DelegatingNode

DelegatingNode()

Protected functions

delegate

protected fun <T : DelegatableNode> delegate(delegatableNode: T): T

In order to properly delegate work to another Modifier.Node, the delegated instance must be created and returned inside of a delegate call. Doing this will ensure that the created node instance follows all of the right lifecycles and is properly discoverable in this position of the node tree.

By using delegate, the delegatableNode parameter is returned from this function for convenience.

This method can be called from within an init block, however the returned delegated node will not be attached until the delegating node is attached. If delegate is called after the delegating node is already attached, the returned delegated node will be attached.

undelegate

protected fun undelegate(instance: DelegatableNode): Unit

This function expects a node which was passed in to delegate for this node, and is currently being delegated to to be passed in as instance. After this function returns, the node will no longer be attached, and will not be an active delegate of this node.

If instance is not an active delegate of this node, this function will throw an IllegalStateException.