Cómo mostrar el contenido de borde a borde en vistas

Prueba la forma de Compose
Jetpack Compose es el kit de herramientas de IU recomendado para Android. Obtén más información para trabajar con el diseño borde a borde en Compose.

Una vez que segmentes tu app para el SDK 35 o versiones posteriores en un dispositivo con Android 15 o versiones posteriores, tu app se mostrará de borde a borde. La ventana abarca todo el ancho y la altura de la pantalla, ya que se dibuja detrás de las barras del sistema. Las barras del sistema incluyen la barra de estado, la barra de leyendas y la barra de navegación.

Muchas apps tienen una barra superior. La barra superior de la app debe extenderse hasta el borde superior de la pantalla y mostrarse detrás de la barra de estado. De manera opcional, la barra superior de la app puede reducirse a la altura de la barra de estado cuando se desplaza el contenido.

Muchas apps también tienen una barra inferior de la app o una barra de navegación inferior. Estas barras también deben extenderse hasta el borde inferior de la pantalla y mostrarse detrás de la barra de navegación. De lo contrario, las apps deben mostrar contenido de desplazamiento detrás de la barra de navegación.

Figura 1. Barras del sistema en un diseño de borde a borde.

Cuando implementes un diseño de borde a borde en tu app, ten en cuenta lo siguiente:

  1. Cómo habilitar una pantalla de borde a borde
  2. Controla cualquier superposición visual.
  3. Considera mostrar scrims detrás de las barras del sistema.
Un ejemplo de imágenes detrás de la barra de estado
Figura 2. Ejemplo de imágenes detrás de la barra de estado.

Habilita la pantalla de borde a borde

Si tu app se orienta al SDK 35 o versiones posteriores, el modo de borde a borde se habilita automáticamente para dispositivos con Android 15 o versiones posteriores.

Para habilitar el modo borde a borde en versiones anteriores de Android, llama manualmente a enableEdgeToEdge en onCreate de tu Activity.

Kotlin

 override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.enableEdgeToEdge(window)
        ...
      }

Java

 @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowCompat.enableEdgeToEdge(getWindow());
        ...
      }

De forma predeterminada, enableEdgeToEdge() hace que las barras del sistema sean transparentes, excepto en el modo de navegación con 3 botones, en el que la barra de estado obtiene una pantalla traslúcida. Los colores de los íconos del sistema y la pantalla se ajustan según el tema claro u oscuro del sistema.

Para habilitar la pantalla de borde a borde en tu app sin usar la función enableEdgeToEdge(), consulta Cómo configurar manualmente la pantalla de borde a borde.

Cómo controlar las superposiciones con inserciones

Es posible que algunas vistas de tu app se dibujen detrás de las barras del sistema, como se muestra en la figura 3.

Puedes abordar las superposiciones reaccionando a las inserciones, que especifican qué partes de la pantalla se cruzan con la IU del sistema, como la barra de navegación o la barra de estado. La intersección puede significar que se muestra sobre el contenido, pero también puede informar a tu app sobre los gestos del sistema.

Los tipos de inserciones que se aplican a la visualización de tu app de borde a borde son los siguientes:

  • Inserciones de barras del sistema: Son las mejores para las vistas en las que se puede presionar y que no deben quedar visualmente ocultas por las barras del sistema.

  • Inserciones de corte de pantalla: Para áreas en las que puede haber un corte de pantalla debido a la forma del dispositivo.

  • Inserciones de gestos del sistema: Para las áreas de navegación por gestos que usa el sistema y que tienen prioridad sobre tu app.

Inserciones de barras del sistema

Las inserciones de la barra del sistema son el tipo de inserciones más común. Representan el área en la que la IU del sistema se muestra en el eje Z sobre tu app. Se usan mejor para mover o rellenar vistas en tu app que se pueden presionar y que no deben quedar visualmente ocultas por las barras del sistema.

Por ejemplo, el botón de acción flotante (BAF) de la figura 3 está parcialmente oculto por la barra de navegación:

Un ejemplo de implementación de borde a borde, pero la barra de navegación cubre el BAF
Figura 3: Barra de navegación que se superpone a un FAB en un diseño de borde a borde.

Para evitar este tipo de superposición visual en el modo de gestos o en el modo de botones, puedes aumentar los márgenes de la vista con getInsets(int) y WindowInsetsCompat.Type.systemBars().

En el siguiente ejemplo de código, se muestra cómo implementar las inserciones de la barra del sistema:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. This solution sets
  // only the bottom, left, and right dimensions, but you can apply whichever
  // insets are appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  v.updateLayoutParams<MarginLayoutParams> {
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. This solution sets only the
  // bottom, left, and right dimensions, but you can apply whichever insets are
  // appropriate to your layout. You can also update the view padding if that's
  // more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Si aplicas esta solución al ejemplo que se muestra en la figura 3, no se produce ninguna superposición visual en el modo de botón, como se muestra en la figura 4:

una barra de navegación traslúcida que no cubre el BAF
Figura 4: Se resolvió la superposición visual en el modo de botón.

Lo mismo se aplica al modo de navegación por gestos, como se muestra en la figura 5:

De borde a borde con navegación por gestos
Figura 5: Se resolvió la superposición visual en el modo de navegación por gestos.

Inserciones de corte de pantalla

Algunos dispositivos tienen cortes en la pantalla. Por lo general, el corte se encuentra en la parte superior de la pantalla y se incluye en la barra de estado. Cuando la pantalla del dispositivo está en modo horizontal, el corte puede estar en el borde vertical. Según el contenido que muestre tu app en la pantalla, debes implementar padding para evitar los cortes de pantalla, ya que, de forma predeterminada, las apps se dibujarán en el corte de pantalla.

Por ejemplo, muchas pantallas de apps muestran una lista de elementos. No ocultes los elementos de la lista con el corte de pantalla o las barras del sistema.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets ->
  val bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
      or WindowInsetsCompat.Type.displayCutout()
  )
  v.updatePadding(
    left = bars.left,
    top = bars.top,
    right = bars.right,
    bottom = bars.bottom,
  )
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(mBinding.recyclerView, (v, insets) -> {
  Insets bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
    | WindowInsetsCompat.Type.displayCutout()
  );
  v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
  return WindowInsetsCompat.CONSUMED;
});

Para determinar el valor de WindowInsetsCompat, toma el OR lógica de las barras del sistema y los tipos de corte de pantalla.

Establece clipToPadding en RecyclerView para que el padding se desplace con los elementos de la lista. Esto permite que los elementos queden detrás de las barras del sistema cuando el usuario se desplaza, como se muestra en el siguiente ejemplo.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

Inserciones de gestos del sistema

Las inserciones de gestos del sistema representan las áreas de la ventana en las que los gestos del sistema tienen prioridad por sobre tu app. Estas áreas se muestran en naranja en la figura 6:

Ejemplo de inserciones de gestos del sistema
Figura 6: Son las inserciones de gestos del sistema.

Al igual que con las inserciones de la barra del sistema, puedes evitar la superposición de las inserciones de gestos del sistema con getInsets(int) y WindowInsetsCompat.Type.systemGestures().

Usa estas inserciones para mover o rellenar las vistas que se pueden deslizar lejos de los bordes. Los casos de uso comunes incluyen hojas inferiores, deslizamientos en juegos y carruseles implementados con ViewPager2.

En Android 10 y versiones posteriores, las inserciones de gestos del sistema contienen una inserción inferior para el gesto de inicio y una inserción izquierda y derecha para los gestos de atrás:

Ejemplo de medidas de inserción de gestos del sistema
Figura 7: Mediciones de inserción de gestos del sistema.

En el siguiente ejemplo de código, se muestra cómo implementar las inserciones de gestos del sistema:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Componentes de Material

Muchos componentes de Material Design para Android (com.google.android.material) basados en Views controlan automáticamente las inserciones, incluidos BottomAppBar, BottomNavigationView, NavigationRailView y NavigationView.

Sin embargo, AppBarLayout no controla automáticamente las inserciones. Agrega android:fitsSystemWindows="true" para controlar las inserciones superiores.

Lee cómo controlar las inserciones con componentes de Material en Compose.

Envío de inserciones compatible con versiones anteriores

Para evitar que los insets se envíen a las vistas secundarias y evitar el exceso de padding, puedes consumir los insets con la constante WindowInsetsCompat.CONSUMED. Sin embargo, en dispositivos que ejecutan Android 10 (nivel de API 29 y versiones anteriores), las inserciones no se envían a los elementos secundarios después de llamar a WindowInsetsCompat.CONSUMED, lo que puede provocar una superposición visual no deseada.

Ejemplo de envío de inserción interrumpido
Figura 8: Ejemplo de envío de inserciones interrumpido. Las inserciones no se envían a las vistas secundarias después de que ViewGroup 1 consume las inserciones en Android 10 (nivel de API 29) y versiones anteriores, lo que hace que TextView 2 se superponga con la barra de navegación del sistema. Sin embargo, las inserciones se envían a las vistas secundarias en Android 11 (nivel de API 30) y versiones posteriores, como se espera.

Para confirmar que las inserciones se envían a los elementos secundarios para todas las versiones compatibles de Android, usa ViewGroupCompat#installCompatInsetsDispatch antes de consumir las inserciones, disponible en AndroidX Core y Core-ktx 1.16.0-alpha01 y versiones posteriores.

Kotlin

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
val rootView = findViewById(R.id.main)
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Java

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
LinearLayout rootView = findViewById(R.id.main);
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView);
Ejemplo de envío de inserción fijo
Figura 9: Se corrigió el envío de inserciones después de llamar a ViewGroupCompat#installCompatInsetsDispatch.

Modo envolvente

Algunos contenidos se experimentan mejor en pantalla completa, lo que brinda al usuario una experiencia más envolvente. Puedes ocultar las barras del sistema para el modo envolvente con las bibliotecas WindowInsetsController y WindowInsetsControllerCompat:

Kotlin

val windowInsetsController =
      WindowCompat.getInsetsController(window, window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

Window window = getWindow();
WindowInsetsControllerCompat windowInsetsController =
      WindowCompat.getInsetsController(window, window.getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());

Consulta Oculta las barras del sistema para el modo envolvente para obtener más información sobre cómo implementar esta función.

Íconos de la barra del sistema

Llamar a enableEdgeToEdge garantiza que los colores de los íconos de la barra del sistema se actualicen cuando cambia el tema del dispositivo.

Cuando vayas de borde a borde, es posible que debas actualizar manualmente los colores de los íconos de la barra del sistema para que contrasten con el fondo de tu app. Por ejemplo, para crear íconos de barra de estado claros, haz lo siguiente:

Kotlin

WindowCompat.getInsetsController(window, window.decorView)
    .isAppearanceLightStatusBars = false

Java

WindowCompat.getInsetsController(window, window.getDecorView())
    .setAppearanceLightStatusBars(false);

Protección de la barra del sistema

Una vez que tu app se segmenta para el SDK 35 o versiones posteriores, se aplica el borde a borde. Las barras de estado del sistema y de navegación por gestos son transparentes, pero la barra de navegación con tres botones es traslúcida. Llama a enableEdgeToEdge para que sea retrocompatible.

Sin embargo, es posible que la configuración predeterminada del sistema no funcione para todos los casos de uso. Consulta la orientación de diseño de las barras del sistema de Android y el diseño de borde a borde para determinar si debes usar barras del sistema transparentes o traslúcidas.

Cómo crear barras del sistema transparentes

Crea una barra de estado transparente segmentando Android 15 (SDK 35) o versiones posteriores, o bien llamando a enableEdgeToEdge() con argumentos predeterminados para versiones anteriores.

Crea una barra de navegación por gestos transparente segmentando Android 15 o versiones posteriores, o bien llamando a enableEdgeToEdge() con argumentos predeterminados para versiones anteriores. En el caso de la barra de navegación con tres botones, establece Window.setNavigationBarContrastEnforced en false; de lo contrario, se aplicará una pantalla traslúcida.

Cómo crear barras del sistema translúcidas

Para crear una barra de estado traslúcida, haz lo siguiente:

  1. Actualiza tu dependencia de androidx-core a 1.16.0-beta01 o una versión posterior.
  2. Encapsula tu diseño XML en androidx.core.view.insets.ProtectionLayout y asígnale un ID.
  3. Accede de forma programática a ProtectionLayout para establecer protecciones, especificando el lado y un GradientProtection para la barra de estado.

<androidx.core.view.insets.ProtectionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_protection"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/item_list"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--items-->

    </ScrollView>

</androidx.core.view.insets.ProtectionLayout>

findViewById<ProtectionLayout>(R.id.list_protection)
    .setProtections(
        listOf(
            GradientProtection(
                WindowInsetsCompat.Side.TOP,
                // Ideally, this is the pane's background color
                paneBackgroundColor
            )
        )
    )

Asegúrate de que el ColorInt que se pasa a GradientProtection coincida con el fondo del contenido. Por ejemplo, un diseño de lista y detalles que se muestra en un dispositivo plegable puede tener diferentes GradientProtections de diferentes colores para el panel de lista y el panel de detalles.

Figura 1. Protección degradada de diferentes colores.

No crees una barra de navegación por gestos translúcida. Para crear una barra de navegación translúcida con tres botones, haz una de las siguientes acciones:

  • Si ya tienes tu diseño incluido en un ProtectionView, puedes pasar un ColorProtection o GradientProtection adicional al método setProtections. Antes de hacerlo, asegúrate de que window.isNavigationBarContrastEnforced = false.
  • De lo contrario, configura window.isNavigationBarContrastEnforced = true. Si tu app llama a enableEdgeToEdge, window.isNavigationBarContrastEnforced = true, es el valor predeterminado.

Otras sugerencias

Sugerencias adicionales para el manejo de inserciones.

Cómo hacer que el contenido de desplazamiento sea de borde a borde

Comprueba que las barras del sistema no oculten el último elemento de la lista en tu RecyclerView o NestedScrollView controlando las inserciones y configurando clipToPadding en false.

En el siguiente video, se muestra un RecyclerView con la pantalla de borde a borde inhabilitada (izquierda) y habilitada (derecha):

Consulta los fragmentos de código en la sección Cómo crear listas dinámicas con RecyclerView para ver un ejemplo de código.

Haz que los diálogos de pantalla completa sean de borde a borde

Para que el diálogo de pantalla completa sea de borde a borde, llama a enableEdgeToEdge en el diálogo.

Kotlin

class MyAlertDialogFragment : DialogFragment() {
    override fun onStart(){
        super.onStart()
        dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) }
    }
    ...
}

Java

public class MyAlertDialogFragment extends DialogFragment {
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            Window window = dialog.getWindow();
            if (window != null) {
                WindowCompat.enableEdgeToEdge(window);
            }
        }
    }
    ...
}

Recursos adicionales

Consulta las siguientes referencias para obtener más información sobre cómo llegar de borde a borde.

Blogs

Diseño

Otra documentación

Videos