Cómo mostrar el contenido de borde a borde en tu app y controlar las inserciones de ventana en Compose

La plataforma de Android se encarga de dibujar la IU del sistema, como el la barra de estado y la barra de navegación. Esta IU del sistema se muestra independientemente de la app que use el usuario.

WindowInsets proporciona información sobre el sistema IU para garantizar que tu app se dibuje en el área correcta y que no esté oscurecida por la IU del sistema.

De borde a borde para dibujar detrás de las barras del sistema
Figura 1: Se usa de borde a borde para dibujar detrás de las barras del sistema.

En Android 14 (nivel de API 34) y versiones anteriores, la IU de tu app no se dibuja debajo las barras del sistema y los cortes de pantalla de forma predeterminada.

En Android 15 (nivel de API 35) y versiones posteriores, tu app se dibuja debajo de las barras del sistema y muestra recortes una vez que se orienta al SDK 35. Esto da como resultado una una experiencia del usuario fluida y que permita que tu aplicación aproveche al máximo el espacio de ventana disponible.

Mostrar contenido detrás de la IU del sistema se denomina borde a borde. En este aprenderás sobre los diferentes tipos de inserciones, cómo ir de borde a borde y cómo usar las APIs de inserción para animar tu IU y garantizar que el contenido de tu app no se obstruyan los elementos de la IU del sistema.

Aspectos básicos de la inserción

Cuando una app se muestra de borde a borde, debes asegurarte de que la IU del sistema no oculte el contenido y las interacciones importantes. Por ejemplo, si un botón se coloca detrás de la barra de navegación, es posible que el usuario no pueda hacer clic en él.

Se especifican el tamaño de la IU del sistema y la información sobre dónde se coloca mediante las inserciones.

Cada parte de la IU del sistema tiene un tipo de inserto correspondiente que describe su tamaño y dónde se coloca. Por ejemplo, las inserciones de la barra de estado proporcionan el tamaño y posición de la barra de estado, mientras que las inserciones de la barra de navegación tamaño y posición de la barra de navegación. Cada tipo de inserto consta de cuatro dimensiones de píxeles: superior, izquierda, derecha e inferior. Estas dimensiones especifican la IU del sistema se extiende desde los lados correspondientes de la ventana de la app. Por lo tanto, para evitar superponerse con ese tipo de IU del sistema, la IU de la app debe insertarse en esa cantidad.

Estos tipos de inserciones integradas de Android están disponibles a través de WindowInsets:

WindowInsets.statusBars

Las inserciones que describen las barras de estado Estas son las barras superiores de la IU del sistema que contienen íconos de notificaciones y otros indicadores.

WindowInsets.statusBarsIgnoringVisibility

Los rellenos de la barra de estado para cuando están visibles Si las barras de estado están ocultas (debido al modo de pantalla completa envolvente), las inserciones de la barra de estado principal estarán vacías, pero no lo estarán.

WindowInsets.navigationBars

Las inserciones que describen las barras de navegación Estas son las barras de la IU del sistema que se encuentran en el lado izquierdo, derecho o inferior del dispositivo y que describen la barra de tareas o los íconos de navegación. Estas pueden cambiar en el tiempo de ejecución en función del método de navegación preferido del usuario y la interacción con la barra de tareas.

WindowInsets.navigationBarsIgnoringVisibility

Las inserciones de la barra de navegación para cuando estén visibles Si las barras de navegación están ocultas (debido a que se ingresó al modo de pantalla completa envolvente), los insertos de la barra de navegación principal estarán vacíos, pero estos insertos no estarán vacíos.

WindowInsets.captionBar

El inserto que describe la decoración de la ventana de la IU del sistema si está en una ventana de formato libre, como la barra de título superior.

WindowInsets.captionBarIgnoringVisibility

La barra de subtítulos se inserta cuando están visibles. Si las barras de subtítulos están ocultas, los recuadros de la barra de subtítulos principal estarán vacíos, pero no lo estarán.

WindowInsets.systemBars

La unión de las inserciones de la barra del sistema, que incluyen las barras de estado, las barras de navegación y la barra de leyendas

WindowInsets.systemBarsIgnoringVisibility

La barra del sistema se inserta para cuando están visibles. Si las barras del sistema están ocultas (debido al modo de pantalla completa envolvente), las inserciones de la barra principal del sistema estarán vacías, pero no lo estarán.

WindowInsets.ime

Inserciones que describen la cantidad de espacio en la parte inferior que ocupa el teclado en pantalla.

WindowInsets.imeAnimationSource

Las inserciones que describen la cantidad de espacio que ocupaba el teclado en software antes de la animación actual del teclado.

WindowInsets.imeAnimationTarget

Son las inserciones que describen la cantidad de espacio que ocupará el teclado en pantalla después de la animación del teclado actual.

WindowInsets.tappableElement

Es un tipo de inserciones que describen información más detallada sobre la IU de navegación y que proporciona la cantidad de espacio en la que el sistema controlará los "toques", y no la app. En el caso de las barras de navegación transparentes con navegación por gestos, algunos elementos de la app se pueden presionar a través de la IU de navegación del sistema.

WindowInsets.tappableElementIgnoringVisibility

Las inserciones de elementos que se pueden presionar para cuando son visibles Si los elementos que se pueden presionar están ocultos (debido al modo de pantalla completa envolvente), las inserciones principales de los elementos que se pueden presionar estarán vacías, pero no estarán vacías.

WindowInsets.systemGestures

Las inserciones que representan la cantidad de inserciones en las que el sistema interceptará gestos para la navegación Las apps pueden especificar manualmente el control de una cantidad limitada de estos gestos a través de Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Es un subconjunto de los gestos del sistema que el sistema siempre controlará y que no se puede inhabilitar a través de Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Las inserciones que representan la cantidad de espacio necesaria para evitar superponerse con un corte de pantalla (muesca o orificio)

WindowInsets.waterfall

Las inserciones que representan las áreas curvas de una pantalla en cascada. Una pantalla en cascada tiene áreas curvas a lo largo de los bordes de la pantalla donde esta comienza a doblarse a lo largo de los lados del dispositivo.

Estos tipos se resumen en tres tipos de inserciones "seguras" que garantizan que el contenido no se oculte:

Estos tipos de inserciones "seguras" protegen el contenido de diferentes maneras, según las inserciones subyacentes de la plataforma:

  • Usa WindowInsets.safeDrawing para proteger el contenido que no se debe dibujar. debajo de cualquier IU del sistema. Este es el uso más común de los insertos: evitar dibujar contenido que la IU del sistema oculta (ya sea de forma parcial o completa).
  • Usa WindowInsets.safeGestures para proteger el contenido con gestos. Esta Evita que los gestos del sistema entren en conflicto con los de la app (como los de la parte inferior) hojas de cálculo, carruseles o juegos).
  • Usa WindowInsets.safeContent como una combinación de WindowInsets.safeDrawing y WindowInsets.safeGestures para garantizar contenido no tiene superposición visual ni de gestos.

Configuración de las inserciones

Para permitir que tu app tenga control total sobre dónde dibuja el contenido, sigue estos pasos de configuración. Sin estos pasos, es posible que tu app dibuje colores negros o sólidos detrás de la IU del sistema, o que no se anime de forma síncrona con el teclado en software.

  1. Orienta el SDK a la versión 35 o una posterior para aplicar el diseño de pantalla completa en Android 15 y versiones posteriores. Tu app se muestra detrás de la IU del sistema. Puedes ajustar la IU de tu app controlando los inserciones.
  2. De manera opcional, llama a enableEdgeToEdge() en Activity.onCreate(), lo que permite que tu app sea de borde a borde en versiones anteriores de Android.
  3. Configura android:windowSoftInputMode="adjustResize" en los campos AndroidManifest.xml entrada. Este parámetro de configuración permite que tu app reciba el tamaño del IME de software como inserciones, que puedes usar para rellenar y diseñar el contenido de forma adecuada cuando el IME aparece y desaparece en tu app.

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

APIs de Compose

Una vez que tu actividad haya tomado el control de la administración de todos los insertos, puedes usar las APIs de Compose para asegurarte de que el contenido no se oculte y que los elementos interactivos no se superpongan con la IU del sistema. Estas APIs también sincronizan el diseño de tu app con cambios en la inserción.

Por ejemplo, este es el método más básico para aplicar los insertos al contenido de toda tu app:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

Este fragmento aplica las inserciones de ventana safeDrawing como padding alrededor del todo el contenido de la app. Si bien esto garantiza que los elementos interactivos no se superponen con la IU del sistema, también significa que ninguna app se dibujará detrás la IU del sistema para lograr un efecto de borde a borde. Para aprovechar al máximo la totalidad ventana, debes ajustar dónde se aplican las inserciones en cada pantalla o componente por componente.

Todos estos tipos de inserción se animan automáticamente con animaciones IME y adaptarlos al nivel de API 21. Por extensión, todos los diseños que usan estas inserciones también se anima automáticamente a medida que cambian los valores de inserción.

Hay dos formas principales de usar estos tipos de inserción para ajustar tu elemento componible diseños: modificadores de padding y modificadores de tamaño de inserción.

Modificadores de padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) aplica la las inserciones de ventana dadas como relleno, que actúan como lo haría Modifier.padding. Por ejemplo, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) aplica las inserciones de dibujo seguras como relleno en los 4 lados.

También hay varios métodos de utilidad integrados para los tipos de inserciones más comunes. Modifier.safeDrawingPadding() es uno de esos métodos, equivalente a Modifier.windowInsetsPadding(WindowInsets.safeDrawing) Hay ejemplos similares para los otros tipos de inserción.

Modificadores de tamaño de inserción

Los siguientes modificadores aplican una cantidad de inserciones de ventana configurando el tamaño del componente para que sea el tamaño de las inserciones:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Aplica el lado inicial de windowInsets como el ancho (como Modifier.width).

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Aplica el lado final de windowInsets como el ancho (como Modifier.width).

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Aplica el lado superior de windowInsets como la altura (como Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Aplica el lado inferior de windowInsets como la altura (como Modifier.height).

Estos modificadores son especialmente útiles para ajustar el tamaño de un Spacer que ocupa la espacio de inserciones:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Consumo de inserciones

Los modificadores de padding de inserción (windowInsetsPadding y asistentes como safeDrawingPadding) consumen automáticamente la parte de las inserciones que aplicado como padding. Mientras se profundiza en el árbol de composición, los modificadores de padding de inset anidados y los modificadores de tamaño de inset saben que algunos de los insets ya fueron consumidos por los modificadores de padding de inset externo y evitan usar la misma parte de los insets más de una vez, lo que generaría demasiado espacio adicional.

Los modificadores de tamaño de inserciones también evitan usar la misma parte de inserciones más de una vez si ya se consumieron. Sin embargo, dado que están cambiando directamente, no consumen las inserciones por sí mismas.

Como resultado, los modificadores de padding anidados cambian automáticamente la cantidad de padding aplicado a cada elemento componible.

En el mismo ejemplo de LazyColumn que antes, se muestra LazyColumn con el modificador imePadding. Dentro de LazyColumn, el último elemento se dimensionado para ser la altura de la parte inferior de las barras del sistema:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Cuando el IME está cerrado, el modificador imePadding() no aplica padding, ya que el IME no tiene altura. Como el modificador imePadding() no aplica relleno, no se consumen inserciones, y la altura de Spacer será del tamaño del lado inferior de las barras del sistema.

Cuando se abre el IME, los inserciones del IME se animan para que coincidan con el tamaño del IME, y el modificador imePadding() comienza a aplicar padding inferior para cambiar el tamaño de LazyColumn a medida que se abre el IME. A medida que el modificador imePadding() comienza a aplicar el padding inferior, también comienza a consumir esa cantidad de inserciones. Por lo tanto, la altura de Spacer comienza a disminuir, ya que el modificador imePadding() ya aplicó parte del espaciado para las barras del sistema. Una vez que el modificador imePadding() aplica una cantidad de padding inferior que es mayor que las barras del sistema, la altura de Spacer es cero.

Cuando se cierra el IME, los cambios se producen a la inversa: Spacer comienza a expandirse desde una altura de cero una vez que imePadding() aplica menos que el lado inferior de las barras del sistema, hasta que finalmente Spacer coincide con la altura del lado inferior de las barras del sistema una vez que el IME se anima por completo.

.
Figura 2: Columna diferida de borde a borde con TextField

Este comportamiento se logra a través de la comunicación entre todos windowInsetsPadding y se pueden ver influenciados en algunos otros maneras.

Modifier.consumeWindowInsets(insets: WindowInsets) también consume inserciones de la misma manera que Modifier.windowInsetsPadding, pero no aplica las inserciones consumidas como padding. Esto resulta útil en combinación con la inserción modificadores de tamaño, para indicar a los hermanos que tiene una cierta cantidad de inserciones ya se consumió:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) se comporta de forma muy de manera similar a la versión con un argumento WindowInsets, pero toma un PaddingValues arbitrario para consumir. Esto es útil para informar cuando el relleno o el espaciado lo proporciona algún otro mecanismo modificadores de padding de inserción, como un Modifier.padding común o una altura fija separadores:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

En los casos en que las inserciones de ventana sin procesar se necesiten sin consumo, usa el WindowInsets directamente o usa WindowInsets.asPaddingValues() para Muestra un PaddingValues de las inserciones que no se ven afectadas por el consumo. Sin embargo, debido a las advertencias que se indican a continuación, es preferible usar el relleno de las inserciones de ventana. y modificadores de tamaño de las inserciones de ventana siempre que sea posible.

Fases de inserciones y Jetpack Compose

Compose usa las APIs principales subyacentes de AndroidX para actualizar y animar las inserciones. que usan las APIs de la plataforma subyacente que administran las inserciones. Debido a esa plataforma, comportamiento, las inserciones tienen una relación especial con las fases de Jetpack Compose

El valor de los rellenos se actualiza después de la fase de composición, pero antes de la fase de diseño. Esto significa que leer el valor de las inserciones en la composición generalmente usa un valor de las inserciones que es un fotograma tarde. El sistema integrado los modificadores descritos en esta página están diseñados para retrasar el uso de los valores de la inserciones hasta la fase de diseño, lo que garantiza que los valores de inserción se usen en en el mismo marco a medida que se actualizan.

Animaciones del IME del teclado con WindowInsets

Puedes aplicar Modifier.imeNestedScroll() a un contenedor de desplazamiento para abrir y Cerrará el IME automáticamente cuando te desplaces a la parte inferior del contenedor.

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

Animación que muestra un elemento de IU que se desplaza desde arriba hacia abajo a fin de dejar lugar para un teclado.
Figura 3: Animaciones del IME

Compatibilidad con la inserción para componentes de Material 3

Para facilitar su uso, muchos de los elementos integrados de Material 3 componibles (androidx.compose.material3) Controlan las inserciones, según cómo se ubican estos elementos en tu app. según las especificaciones de Material.

Elementos componibles que manejan inserciones

A continuación, se muestra una lista de los componentes de Material que controlan automáticamente las inserciones.

Barras de la app

Contenedores de contenido

Scaffold

De forma predeterminada, Scaffold proporciona inserciones como el parámetro paddingValues para que las consumas y las uses. Scaffold no aplica las inserciones al contenido. esta responsabilidad es tuya. Por ejemplo, para consumir estos inserciones con un LazyColumn dentro de un Scaffold, haz lo siguiente:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Cómo anular los rellenos predeterminados

Puedes cambiar el parámetro windowInsets que se pasa al elemento componible para configurar su comportamiento. Este parámetro puede ser un tipo diferente de inserto de ventana para aplicar en su lugar, o bien se puede inhabilitar pasando una instancia vacía: WindowInsets(0, 0, 0, 0).

Por ejemplo, para inhabilitar el manejo de inserciones en LargeTopAppBar, Configura el parámetro windowInsets en una instancia vacía:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Interoperabilidad con las inserciones del sistema de View

Es posible que debas anular los rellenos predeterminados cuando tu pantalla tenga elementos View y código de Compose en la misma jerarquía. En este caso, debes ser explícito en cuál debe consumir las inserciones y cuál debe ignorarlas.

Por ejemplo, si tu diseño más externo es de Android View, deberías consumirán las inserciones del sistema de View y las ignorarán en Compose. Como alternativa, si el diseño más externo es un elemento componible, debes consumir el las inserciones en Compose y rellena los elementos componibles AndroidView según corresponda.

De forma predeterminada, cada ComposeView consume todas las inserciones en el WindowInsetsCompat nivel de consumo. Para cambiar este comportamiento predeterminado, establece ComposeView.consumeWindowInsets en false.

Recursos