WindowInsetsAnimation


Provides properties related to animating WindowInsetsRulers.

Summary

Public properties

Float

The translucency of the animating window.

Cmn
Long

The duration of the animation in milliseconds.

Cmn
Float

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

Cmn
Boolean

True when the Window Insets are currently being animated.

Cmn
Boolean

True when the Window Insets are visible.

Cmn
RectRulers

The starting insets values of the animation when the insets are animating (WindowInsetsAnimation.isAnimating is true).

Cmn
RectRulers

The ending insets values of the animation when the insets are animating (WindowInsetsAnimation.isAnimating is true).

Cmn

Public properties

alpha

val alphaFloat

The translucency of the animating window. This is used when Window Insets animate by fading and can be used to have content match the fade.

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.layout.WindowInsetsRulers
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 = WindowInsetsRulers.StatusBars.current.top.current(0f).roundToInt()
            val bottom =
                WindowInsetsRulers.NavigationBars.current.bottom.current(0f).roundToInt()
            measurables[0].measure(Constraints.fixed(width, top)).placeWithLayer(0, 0) {
                alpha = WindowInsetsRulers.StatusBars.getAnimation(this@layout).alpha
            }
            measurables[2].measure(Constraints.fixed(width, height - bottom)).place(0, bottom)
            measurables[1].measure(Constraints.fixed(width, bottom - top)).placeWithLayer(
                0,
                top,
            ) {
                alpha = WindowInsetsRulers.NavigationBars.getAnimation(this@layout).alpha
            }
        }
    },
)

durationMillis

val durationMillisLong

The duration of the animation in milliseconds.

fraction

val fractionFloat

The current fraction of the animation if the Window Insets are being animated or 0 if isAnimating is false. When animating, fraction typically ranges between 0 at the start to 1 at the end, but it may go out of that range if an interpolator causes the fraction to overshoot the range.

isAnimating

val isAnimatingBoolean

True when the Window Insets are currently being animated.

isVisible

val isVisibleBoolean

True when the Window Insets are visible. For example, for StatusBars, when a status bar is shown, isVisible will be true. When the status bar is hidden, isVisible will be false. isVisible remains true during animations.

source

val sourceRectRulers

The starting insets values of the animation when the insets are animating (WindowInsetsAnimation.isAnimating is true). When the insets are not animating, no ruler values will be provided.

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.TextField
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.WindowInsetsRulers
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.dp

Column(Modifier.fillMaxSize()) {
    // TextField will show the IME when it is focused.
    TextField("HelloWorld", {}, Modifier.fillMaxWidth())
    // When the IME shows, animate the content to align with the top of the IME.
    // When the IME hides, animate the content to the top of the Box.
    val verticalPosition = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()
    Box(
        Modifier.fillMaxSize().background(Color.Yellow).layout { measurable, constraints ->
            if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
                layout(constraints.maxWidth, constraints.maxHeight) {
                    val placeable =
                        measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
                    val ime = WindowInsetsRulers.Ime
                    val animationProperties = ime.getAnimation(this)
                    val height = constraints.maxHeight.toFloat()
                    val sourceBottom = animationProperties.source.bottom.current(height)
                    val targetBottom = animationProperties.target.bottom.current(height)
                    val targetPosition =
                        if (!animationProperties.isVisible || sourceBottom < targetBottom) {
                            // IME is either not visible or animating away
                            0f
                        } else if (animationProperties.isAnimating) {
                            // IME is visible and animating
                            targetBottom - placeable.height
                        } else {
                            // IME is visible and not animating, so use the IME poosition
                            ime.current.bottom.current(height) - placeable.height
                        }
                    if (targetPosition != verticalPosition.targetValue) {
                        coroutineScope.launch { verticalPosition.animateTo(targetPosition) }
                    }
                    placeable.place(0, verticalPosition.value.roundToInt())
                }
            } else {
                // It should only get here if inside scrollable content or aligning to an
                // alignment
                // line.
                val placeable = measurable.measure(constraints)
                layout(placeable.width, placeable.height) { placeable.place(0, 0) }
            }
        }
    ) {
        // content
        Box(Modifier.size(100.dp).background(Color.Blue))
    }
}

target

val targetRectRulers

The ending insets values of the animation when the insets are animating (WindowInsetsAnimation.isAnimating is true). When the insets are not animating, no ruler values will be provided.

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.TextField
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.WindowInsetsRulers
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.dp

Column(Modifier.fillMaxSize()) {
    // TextField will show the IME when it is focused.
    TextField("HelloWorld", {}, Modifier.fillMaxWidth())
    // When the IME shows, animate the content to align with the top of the IME.
    // When the IME hides, animate the content to the top of the Box.
    val verticalPosition = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()
    Box(
        Modifier.fillMaxSize().background(Color.Yellow).layout { measurable, constraints ->
            if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
                layout(constraints.maxWidth, constraints.maxHeight) {
                    val placeable =
                        measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
                    val ime = WindowInsetsRulers.Ime
                    val animationProperties = ime.getAnimation(this)
                    val height = constraints.maxHeight.toFloat()
                    val sourceBottom = animationProperties.source.bottom.current(height)
                    val targetBottom = animationProperties.target.bottom.current(height)
                    val targetPosition =
                        if (!animationProperties.isVisible || sourceBottom < targetBottom) {
                            // IME is either not visible or animating away
                            0f
                        } else if (animationProperties.isAnimating) {
                            // IME is visible and animating
                            targetBottom - placeable.height
                        } else {
                            // IME is visible and not animating, so use the IME poosition
                            ime.current.bottom.current(height) - placeable.height
                        }
                    if (targetPosition != verticalPosition.targetValue) {
                        coroutineScope.launch { verticalPosition.animateTo(targetPosition) }
                    }
                    placeable.place(0, verticalPosition.value.roundToInt())
                }
            } else {
                // It should only get here if inside scrollable content or aligning to an
                // alignment
                // line.
                val placeable = measurable.measure(constraints)
                layout(placeable.width, placeable.height) { placeable.place(0, 0) }
            }
        }
    ) {
        // content
        Box(Modifier.size(100.dp).background(Color.Blue))
    }
}