AnimatableInsetsRulers


Rulers for Window Insets that can be animated.

This includes the position of the Window Insets without regard for whether the insets are visible as well as the start and end rulers of any current animation. Developers can read whether the insets are current visible, isAnimating, the fraction, interpolatedFraction, interpolator, and duration of the animation when animating by using a DelegatableNode.

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.material.TextField
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.InsetsRulers
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.fontscaling.MathUtils.lerp

class LandOnImeModifierNode : Modifier.Node(), LayoutModifierNode {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        return if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val width = constraints.maxWidth
            val height = constraints.maxHeight
            layout(width, height) {
                val node = this@LandOnImeModifierNode
                val placeable = measurable.measure(constraints)
                val bottom =
                    with(node) {
                        if (!InsetsRulers.Ime.isAnimating(node)) {
                            if (InsetsRulers.Ime.isVisible(node)) {
                                InsetsRulers.Ime.bottom.current(Float.NaN)
                            } else {
                                Float.NaN
                            }
                        } else {
                            val start = InsetsRulers.Ime.source.bottom.current(Float.NaN)
                            val end = InsetsRulers.Ime.target.bottom.current(Float.NaN)
                            val fraction = InsetsRulers.Ime.interpolatedFraction(node)
                            if (start.isNaN() || end.isNaN()) {
                                Float.NaN // don't know where it is animating
                            } else if (start > end) { // animate IME up
                                lerp(placeable.height.toFloat(), end, fraction)
                            } else { // animating down
                                lerp(start, placeable.height.toFloat(), fraction)
                            }
                        }
                    }
                val y =
                    if (bottom.isNaN()) {
                        0 // place at the top
                    } else if (bottom > height) {
                        height - placeable.height // place at the bottom
                    } else { // place somewhere in the middle
                        bottom.roundToInt() - placeable.height
                    }
                placeable.place(0, y)
            }
        } else {
            // Can't work with the rulers if we can't take the full size
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
        }
    }
}

class LandOnImeElement : ModifierNodeElement<LandOnImeModifierNode>() {
    override fun create(): LandOnImeModifierNode = LandOnImeModifierNode()

    override fun hashCode(): Int = 0

    override fun equals(other: Any?): Boolean = other is LandOnImeElement

    override fun update(node: LandOnImeModifierNode) {}
}

Box(Modifier.fillMaxSize()) {
    Box(LandOnImeElement()) {
        // This content will rest at the top, but then animate to land on the IME when it is
        // animated in.
        Box(Modifier.size(100.dp).background(Color.Blue))
    }
    TextField(
        "Hello World",
        onValueChange = {},
        Modifier.safeDrawingPadding().align(Alignment.BottomEnd)
    )
}

Summary

Public functions

Float

Returns the translucency of the animating window or 1 if isAnimating is false.

android
Float

Returns the translucency of the animating window or 1 if isAnimating is false.

android
Long

The duration of the animation or 0 if isAnimating is false.

android
Long

The duration of the animation or 0 if isAnimating is false.

android
Float

Return the current fraction of the animation if the Window Insets are being animated or 0 if isAnimating is false.

android
Float

Return the current fraction of the animation if the Window Insets are being animated or 0 if isAnimating is false.

android
Float

The current interpolated fraction of the animation, or 0 if isAnimating is false.

android
Float

The current interpolated fraction of the animation, or 0 if isAnimating is false.

android
Interpolator?

The Interpolator that is being used in the animation of the Window Insets or null if isAnimating is false.

android
Interpolator?

The Interpolator that is being used in the animation of the Window Insets or null if isAnimating is false.

android
open Boolean

True when the Window Insets are currently being animated.

android
open Boolean

True when the Window Insets are currently being animated.

android
Boolean

Return true when the Window Insets are visible.

android
Boolean

Return true when the Window Insets are visible.

android
open String
android

Public properties

open HorizontalRuler

The bottom position of the rectangle

android
open VerticalRuler

The left position of the rectangle.

android
open VerticalRuler

The right position of the rectangle

android
RectRulers

The value of the Window Insets when they are visible.

android
open RectRulers

The starting insets value of the animation when isAnimating is true.

android
open RectRulers

The ending insets value of the animation when isAnimating is true.

android
open HorizontalRuler

The top position of the rectangle.

android

Public functions

alpha

fun alpha(node: DelegatableNode): Float

Returns the translucency of the animating window or 1 if isAnimating is false.

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.InsetsRulers
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Constraints

Layout(
    modifier = Modifier.fillMaxSize(),
    content = {
        Box(Modifier.background(Color.Blue)) // status bar background
        Box(Modifier.background(Color.Yellow)) // navigation bar background on bottom
        Box(Modifier.background(Color.White)) // content between top and bottom
    },
    measurePolicy = { measurables, constraints ->
        val width = constraints.maxWidth
        val height = constraints.maxHeight
        layout(width, height) {
            val top = InsetsRulers.StatusBars.top.current(0f).roundToInt()
            val bottom = InsetsRulers.NavigationBars.bottom.current(0f).roundToInt()
            measurables[0].measure(Constraints.fixed(width, top)).placeWithLayer(0, 0) {
                alpha = InsetsRulers.StatusBars.alpha(this@layout)
            }
            measurables[2].measure(Constraints.fixed(width, height - bottom)).place(0, bottom)
            measurables[1].measure(Constraints.fixed(width, bottom - top)).placeWithLayer(
                0,
                top
            ) {
                alpha = InsetsRulers.NavigationBars.alpha(this@layout)
            }
        }
    }
)
Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

See also
getAlpha

alpha

fun alpha(placementScope: Placeable.PlacementScope): Float

Returns the translucency of the animating window or 1 if isAnimating is false.

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.InsetsRulers
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Constraints

Layout(
    modifier = Modifier.fillMaxSize(),
    content = {
        Box(Modifier.background(Color.Blue)) // status bar background
        Box(Modifier.background(Color.Yellow)) // navigation bar background on bottom
        Box(Modifier.background(Color.White)) // content between top and bottom
    },
    measurePolicy = { measurables, constraints ->
        val width = constraints.maxWidth
        val height = constraints.maxHeight
        layout(width, height) {
            val top = InsetsRulers.StatusBars.top.current(0f).roundToInt()
            val bottom = InsetsRulers.NavigationBars.bottom.current(0f).roundToInt()
            measurables[0].measure(Constraints.fixed(width, top)).placeWithLayer(0, 0) {
                alpha = InsetsRulers.StatusBars.alpha(this@layout)
            }
            measurables[2].measure(Constraints.fixed(width, height - bottom)).place(0, bottom)
            measurables[1].measure(Constraints.fixed(width, bottom - top)).placeWithLayer(
                0,
                top
            ) {
                alpha = InsetsRulers.NavigationBars.alpha(this@layout)
            }
        }
    }
)
See also
getAlpha

durationMillis

fun durationMillis(node: DelegatableNode): Long

The duration of the animation or 0 if isAnimating is false.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

See also
getInterpolator

durationMillis

fun durationMillis(placementScope: Placeable.PlacementScope): Long

The duration of the animation or 0 if isAnimating is false.

See also
getInterpolator

fraction

fun fraction(node: DelegatableNode): Float

Return the current fraction of the animation if the Window Insets are being animated or 0 if isAnimating is false.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

See also
getFraction

fraction

fun fraction(placementScope: Placeable.PlacementScope): Float

Return the current fraction of the animation if the Window Insets are being animated or 0 if isAnimating is false.

See also
getFraction

interpolatedFraction

fun interpolatedFraction(node: DelegatableNode): Float

The current interpolated fraction of the animation, or 0 if isAnimating is false.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

interpolatedFraction

fun interpolatedFraction(placementScope: Placeable.PlacementScope): Float

The current interpolated fraction of the animation, or 0 if isAnimating is false.

interpolator

fun interpolator(node: DelegatableNode): Interpolator?

The Interpolator that is being used in the animation of the Window Insets or null if isAnimating is false.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

See also
getInterpolator

interpolator

fun interpolator(placementScope: Placeable.PlacementScope): Interpolator?

The Interpolator that is being used in the animation of the Window Insets or null if isAnimating is false.

See also
getInterpolator

isAnimating

open fun isAnimating(node: DelegatableNode): Boolean

True when the Window Insets are currently being animated. source and target will be set while isAnimating is true.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

isAnimating

open fun isAnimating(placementScope: Placeable.PlacementScope): Boolean

True when the Window Insets are currently being animated. source and target will be set while isAnimating is true.

isVisible

fun isVisible(node: DelegatableNode): Boolean

Return true when the Window Insets are visible.

Parameters
node: DelegatableNode

The DelegatableNode that the is being used to read the value.

See also
getInsets

isVisible

fun isVisible(placementScope: Placeable.PlacementScope): Boolean

Return true when the Window Insets are visible.

See also
getInsets

toString

open fun toString(): String

Public properties

bottom

open val bottomHorizontalRuler

The bottom position of the rectangle

left

open val leftVerticalRuler

The left position of the rectangle.

right

open val rightVerticalRuler

The right position of the rectangle

rulersIgnoringVisibility

val rulersIgnoringVisibilityRectRulers

The value of the Window Insets when they are visible. Ime never provides this value.

source

open val sourceRectRulers

The starting insets value of the animation when isAnimating is true.

target

open val targetRectRulers

The ending insets value of the animation when isAnimating is true.

top

open val topHorizontalRuler

The top position of the rectangle.