Cómo cumplir con los requisitos de borde a borde de Android 15

1. Antes de comenzar

SociaLite hace una demostración de cómo usar diversas APIs de la plataforma de Android para implementar características que comúnmente se ven en apps de redes sociales, a la vez que aprovecha una variedad de APIs de Jetpack para lograr funcionalidades complejas que funcionan de manera confiable en más dispositivos y requieren menos código.

En este codelab, se explica el proceso para hacer que la app de SociaLite sea compatible con los requisitos de borde a borde de Android 15 y que el atributo borde a borde de la app sea retrocompatible. Después de cumplir con los requisitos de borde a borde, SociaLite se verá de la siguiente manera, según el dispositivo y el modo de navegación:

La app de SociaLite en la navegación con tres botones.

La app de SociaLite en la navegación por gestos.

SociaLite con navegación con tres botones

SociaLite con navegación por gestos

La app de SociaLite en un dispositivo de pantalla grande.

SociaLite en un dispositivo de pantalla grande

Requisitos previos

  • Conocimientos básicos de Kotlin
  • Haber completado el codelab Configuración de Android Studio o saber cómo usar Android Studio y probar apps en un emulador o en un dispositivo físico que ejecute Android 15

Qué aprenderás

  • Cómo controlar los cambios borde a borde de Android 15
  • Cómo hacer que el atributo borde a borde de la app sea retrocompatible

Requisitos

  • La versión más reciente de Android Studio
  • Un dispositivo de prueba o un emulador que ejecuten Android 15 Beta 1 o una versión posterior
  • Un SDK Android 15 Beta 1 o una versión posterior

2. Obtén el código de partida

  1. Descarga el código de partida de GitHub.

Como alternativa, puedes clonar el repositorio y verificar la rama codelab_improve_android_experience_2024.

$ git clone git@github.com:android/socialite.git
$ cd socialite
$ git checkout codelab_improve_android_experience_2024
  1. Abre SociaLite en Android Studio y ejecuta la app en tu dispositivo o emulador con Android 15. Verás una pantalla que se ve como una de las siguientes:

SociaLite con navegación con tres botones.

SociaLite con navegación por gestos.

Navegación con tres botones

Navegación por gestos

SociaLite en un dispositivo de pantalla grande.

Pantalla grande

  1. En la página Chats, selecciona una de las conversaciones, como la que tiene la imagen de un perro.

Mensaje de chat sobre perros, con navegación con tres botones

Mensaje de chat sobre perros, con navegación por gestos

Mensaje de chat sobre perros, con navegación con tres botones

Mensaje de chat sobre perros, con navegación por gestos

3. Haz que tu app cumpla con los requisitos de borde a borde en Android 15

¿En qué consiste el borde a borde?

Las apps permiten dibujar detrás de las barras del sistema y, de este modo, posibilitar una experiencia del usuario refinada, que usa completamente el espacio de la pantalla. A esto nos referimos cuando hablamos de borde a borde.

GIF de una app borde a borde

Cómo controlar los cambios borde a borde de Android 15

Antes de Android 15, de forma predeterminada, la disposición de la IU de la app estaba limitada de modo tal que evitaba las áreas de barras del sistema, como la barra de estado y la de navegación. Hoy en día, las apps pueden tienen habilitado el formato de borde a borde de forma predeterminada. Según la app, habilitar la opción podría ser un proceso fácil o no.

A partir de Android 15, tu app será de borde a borde de forma predeterminada. Verás las siguientes opciones predeterminadas:

  • La barra de navegación con tres botones es traslúcida.
  • La barra de navegación por gestos es transparente.
  • La barra de estado es transparente.
  • A menos que el contenido aplique inserciones o relleno, el contenido se dispondrá detrás de las barras del sistema, como la barra de navegación, la barra de estado y la barra de leyendas.

Esto asegura que la característica borde a borde no se pase por alto como medio para aumentar la calidad de la app y reduce el trabajo que esta requiere para implementar el borde a borde. Sin embargo, este cambio puede afectar negativamente la app. Verás dos ejemplos de este efecto en SociaLite luego de actualizar el SDK de destino en Android 15.

Cómo cambiar el valor de SDK de destino en Android 15

  1. Dentro del archivo build.gradle de la app SociaLite, cambia el destino y compila versiones de SDK en Android 15 o VanillaIceCream.

Si tomas este codelab antes del lanzamiento estable de Android 15, el código se verá así:

android {
    namespace = "com.google.android.samples.socialite"
    compileSdkPreview = "VanillaIceCream"

    defaultConfig {
        applicationId = "com.google.android.samples.socialite"
        minSdk = 21
        targetSdkPreview = "VanillaIceCream"
        ...
    }
...
}

Si tomas este codelab después del lanzamiento estable de Android 15, el código se verá así:

android {
    namespace = "com.google.android.samples.socialite"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.google.android.samples.socialite"
        minSdk = 21
        targetSdk = 35
        ...
    }
...
}
  1. Vuelve a compilar SociaLite y observa los siguientes problemas:
  • La protección en segundo plano de la navegación con tres botones no coincide con la barra de navegación. Respecto de la navegación por gestos, la pantalla Chats se ve de borde a borde sin ninguna intervención de tu parte. Sin embargo, está la protección en segundo plano de la navegación con tres botones que se debería quitar.

Pantalla Chats con navegación con tres botones.

Pantalla Chats con navegación por gestos.

Pantalla Chats con navegación con tres botones

Pantalla Chats con navegación por gestos

  • IU obstruida. En una conversación, las barras de navegación obstruyen los elementos de la IU de la parte inferior. Esto es más evidente en la navegación con tres botones.

Mensaje de chat sobre perros, con navegación con tres botones.

Mensaje de chat sobre perros, con navegación por gestos.

Mensaje de chat sobre perros, con navegación con tres botones

Mensaje de chat sobre perros, con navegación por gestos

Cómo corregir SociaLite

Para quitar la protección en segundo plano predeterminada de la navegación con tres botones, sigue estos pasos:

  1. En el archivo MainActivity.kt, configura la propiedad window.isNavigationBarContrastEnforced en "false" para quitar la protección en segundo plano predeterminada.
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)
        setContent {
            // Add this block:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                window.isNavigationBarContrastEnforced = false
            }
        }
    }
    ...
}

window.isNavigationBarContrastEnforced garantiza que la barra de navegación tenga el contraste suficiente cuando se solicita un segundo plano completamente transparente. Cuando se configura este atributo como "false", efectivamente configuras el segundo plano de la navegación con tres botones en transparente. window.isNavigationBarContrastEnforced solo tendrá efecto en la navegación con tres botones y no afectará la navegación por gestos.

  1. Vuelve a ejecutar la aplicación y ve una de las conversaciones en tu dispositivo con Android 15. Las pantallas Timeline, Chats y Settings ahora aparecen de borde a borde. Las pantallas de la app NavigationBar (con los botones Timeline, Chats y Settings) se disponen detrás de la barra de navegación con tres botones transparente del sistema.

La pantalla Chats con navegación con tres botones y disposición en bandas eliminadas.

Conversación sobre perros en navegación por gestos.

Pantalla Chats con disposición en bandas eliminada

No hay cambios en la navegación por gestos

Sin embargo, observa que las barras del sistema aún obstruyen el InputBar de la conversación. Para corregir este problema, tienes que controlar adecuadamente las inserciones.

Conversación sobre perros en navegación con tres botones.

Conversación sobre perros en navegación por gestos.

Conversación sobre perros en navegación con tres botones. La barra de navegación del sistema obstruye el campo de entrada en la parte inferior.

Conversación sobre perros en navegación por gestos. La barra de navegación del sistema obstruye el campo de entrada en la parte inferior.

En SociaLite, el objeto InputBar está oculto. En la práctica, puede que encuentres elementos en la parte superior, inferior, izquierda y derecha ocultos cuando rotas al modo horizontal, o bien cuando estás en un dispositivo con pantalla grande. Tienes que pensar cómo controlar las inserciones para todos estos casos de uso. Para SociaLite, aplicas relleno para transmitir el contenido del InputBar que se puede tocar.

Para aplicar inserciones para corregir la IU obstruida, sigue estos pasos:

  1. Navega al archivo ui/chat/ChatScreen.kt y, luego, busca el elemento ChatContent componible alrededor de la línea 178, que contiene la IU de la pantalla de conversación. ChatContent aprovecha Scaffold para desarrollar la IU de manera sencilla. De forma predeterminada, Scaffold proporciona información sobre la IU del sistema, como qué tan profundo se encuentran las barras del sistema, como inserciones que puedes consumir con los valores de relleno de Scaffold (el parámetro innerPadding). Agrega relleno con innerPadding de Scaffold en InputBar.
  2. Busca InputBar dentro de ChatContent cerca de la línea 214. Es un elemento componible personalizado que crea la IU para que los usuarios escriban mensajes. La vista previa es similar a esta:

PreviewInputBar.

InputBar toma un contentPadding y lo aplica como relleno en el elemento Row componible que contiene el resto de la IU. El relleno se aplicará a todos los lados del elemento componible Row. Puedes ver esto alrededor de la línea 432. Este es el elemento InputBar componible para referencia (no agregues este código):

// Don't add this code because it's only for reference.
@Composable
private fun InputBar(
    contentPadding: PaddingValues,
    ...,
) {
    Surface(...) {
        Row(
            modifier = Modifier
                .padding(contentPadding)
            ...
        ) {
            IconButton(...) { ... } // take picture
            IconButton(...) { ... } // attach picture
            TextField(...) // write message
            FilledIconButton(...){ ... } // send message
            }
        }
    }
}
  1. Vuelve al InputBar dentro de ChatContent y cambia contentPadding para consumir las inserciones de barras del sistema. Esto es alrededor de la línea 220.
InputBar(
    ...
    contentPadding = innerPadding, //Add this line.
    // contentPadding = PaddingValues(0.dp), // Remove this line.
    ...
 )
  1. Vuelve a ejecutar la app en tu dispositivo con Android 15.

Conversación sobre perros en navegación con tres botones.

Conversación sobre perros en navegación por gestos.

Conversación sobre perros en navegación con tres botones, con inserciones aplicadas incorrectamente.

Conversación sobre perros en navegación por gestos, con inserciones aplicadas incorrectamente.

El relleno de la parte inferior se aplicó de modo tal que las barras del sistema ya no ocultan los botones. Sin embargo, también se aplicó el relleno de la parte superior. El relleno de la parte superior comprende la profundidad de TopAppBar y de la barra del sistema. Scaffold pasa los valores de relleno a su contenido para que pueda evitar la barra superior de la app, además de las barras del sistema.

  1. Para corregir el relleno de la parte superior, crea una copia de innerPadding PaddingValues, configura el relleno de la parte superior en 0.dp y pasa la copia modificada a contentPadding.
InputBar(
    ...
    contentPadding = innerPadding.copy(layoutDirection, top = 0.dp), //Add this line.
    // contentPadding = innerPadding, // Remove this line.
    ...
 )
  1. Vuelve a ejecutar la app en tu dispositivo con Android 15.

Conversación sobre perros en navegación con tres botones.

Conversación sobre perros en navegación por gestos.

Conversación sobre perros en navegación con tres botones, con inserciones correctamente aplicadas.

Conversación sobre perros en navegación por gestos, con inserciones aplicadas incorrectamente.

¡Felicitaciones! Hiciste que SociaLite sea compatible con los cambios de la plataforma borde a borde de Android 15. A continuación, aprenderás a hacer que SociaLite sea retrocompatible de borde a borde.

4. Haz que SociaLite sea retrocompatible de borde a borde

SociaLite ahora es borde a borde en Android 15, pero aún no lo es en dispositivos Android anteriores. Para que SociaLite sea borde a borde en dispositivos Android anteriores, llama a enableEdgeToEdge antes de configurar el contenido en el archivo MainActivity.kt.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        enableEdgeToEdge() // Add this line.
        window.isNavigationBarContrastEnforced = false
        super.onCreate(savedInstanceState)
        setContent {... }
    }
}

La importación para enableEdgeToEdge es import androidx.activity.enableEdgeToEdge. La dependencia es AndroidX Activity 1.8.0 o una versión posterior.

Si quieres obtener una descripción detallada para hacer que tu app sea retrocompatible de borde a borde y cómo controlar las inserciones, consulta las siguientes guías:

Aquí concluye la sección sobre borde a borde de la ruta de aprendizaje. La sección siguiente es opcional y analiza otras consideraciones de la integración borde a borde que podrían corresponder a tu aplicación.

5. Opcional: Consideraciones adicionales de la integración borde a borde

Cómo controlar las inserciones en diversas arquitecturas

Componentes

Es probable que hayas notado que muchos componentes de SociaLite no se modificaron luego de que cambiáramos el valor del SDK de destino. SociaLite está diseñado según prácticas recomendadas, de modo que procesar este cambio en la plataforma es sencillo. Las prácticas recomendadas incluyen lo siguiente:

Cómo desplazarse por el contenido

Tu app podría contener listas y, con el cambio de Android 15, puede que las barras de navegación del sistema obstruyan el último elemento de la lista.

App con el último elemento de la lista obstruido por la navegación con tres botones.

Muestra que la navegación con tres botones obstruyó el último elemento de la lista.

Cómo desplazarse por el contenido con Compose

En Compose, usa contentPadding de LazyColumn para agregar un espacio al último elemento, a menos que uses TextField:

Scaffold { innerPadding ->
    LazyColumn(
        contentPadding = innerPadding
    ) {
        // Content that does not contain TextField
    }
}

App con último elemento de la lista que no está obstruido por la navegación con tres botones.

Muestra que la navegación con tres botones no obstruye el último elemento de la lista.

Para TextField, usa un Spacer para dibujar el último TextField en un LazyColumn. Para obtener más información, consulta Consumo de inserciones.

LazyColumn(
    Modifier.imePadding()
) {
    // Content with TextField
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Cómo desplazarse por el contenido con Views

Para RecyclerView o NestedScrollView, agrega android:clipToPadding="false".

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

Proporciona rellenos en las partes izquierda, derecha e inferior de inserciones de ventana con setOnApplyWindowInsetsListener:

ViewCompat.setOnApplyWindowInsetsListener(binding.recycler) { v, insets ->
    val i = insets.getInsets(
        WindowInsetsCompat.Type.systemBars() + WindowInsetsCompat.Type.displayCutout()
    )
    v.updatePadding(
        left = i.left,
        right = i.right,
        bottom = i.bottom + bottomPadding,
    )
    WindowInsetsCompat.CONSUMED
}

Cómo usar LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS

Antes de segmentar SDK 35, SocialLite se veía de esta manera cuando el dispositivo estaba horizontal y se ve que el borde izquierdo tiene un cuadro blanco grande para tener en cuenta el recorte de la cámara. En la navegación con tres botones, los botones se encuentran en el lateral derecho.

La app de SociaLite en orientación horizontal.

Después de segmentar SDK 35, SociaLite se verá así, donde el borde izquierdo ya no tiene un cuadro blanco grande para tener en cuenta el recorte de la cámara. Para lograr este efecto, Android automáticamente configura LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS. La app de SociaLite en orientación horizontal.

Según tu app, es posible que quieras controlar las inserciones aquí.

Para hacerlo en SociaLite, sigue estos pasos:

  1. En el archivo ui/ContactRow.kt, busca el elemento componible Row.
  2. Modifica el relleno para tener en cuenta el corte de pantalla.
@Composable
fun ChatRow(
   chat: ChatDetail,
   onClick: (() -> Unit)?,
   modifier: Modifier = Modifier,
) {
   // Add layoutDirection, displayCutout, startPadding, and endPadding.
   val layoutDirection = LocalLayoutDirection.current
   val displayCutout = WindowInsets.displayCutout.asPaddingValues()
   val startPadding = displayCutout.calculateStartPadding(layoutDirection)
   val endPadding = displayCutout.calculateEndPadding(layoutDirection)
   Row(
       modifier = modifier
           ...
           // .padding(16.dp) // Remove this line.
           // Add this block:
           .padding(
               PaddingValues(
                   top = 16.dp,
                   bottom = 16.dp,
                   // Ensure content is not occluded by display cutouts
                   // when rotating the device.
                   start = startPadding.coerceAtLeast(16.dp),
                   end = endPadding.coerceAtLeast(16.dp)
               )
           ),
       ...
   ) { ... }

Después de controlar los cortes de pantalla, SociaLite se verá así:

La app de SociaLite en orientación horizontal.

Puedes probar diversas configuraciones de corte de pantalla en la pantalla Opciones para desarrolladores en Corte de pantalla.

Si tu aplicación tiene una ventana no flotante (por ejemplo, una actividad) que usa LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER o LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, Android interpretará estos modos de corte para que inicien LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS en Android 15 Beta 2. Anteriormente, en Android 15 Beta 1, la app habría fallado.

Las barras de leyendas también son barras del sistema

Una barra de leyendas también es una barra del sistema, ya que describe la decoración de ventana de IU del sistema de una ventana de formato libre, como la barra de títulos superior. Puedes ver la barra de leyendas dentro de un emulador de escritorio en Android Studio. En la siguiente captura de pantalla, la barra de leyendas se encuentra en la parte superior de la aplicación.

Emulador que muestra una barra de leyendas.

En Compose, si usas PaddingValues, safeContent, safeDrawing o el objeto WindowInsets.systemBars incorporado de Scaffold, tu app se mostrará según lo esperado. Sin embargo, si controlas las inserciones con statusBar, es posible que el contenido de la app no se muestre según lo esperado porque la barra de estado no tiene en cuenta la barra de leyendas.

En Views, si controlas las inserciones manualmente con WindowInsetsCompat.systemBars, tu app se mostrará según lo esperado. Si controlas las inserciones de forma manual con WindowInsetsCompat.statusBars, es probable que la app no se muestre según lo esperado porque las barras de estado no son barras de leyendas.

Apps en modo envolvente

En su gran mayoría, el cumplimiento borde a borde de Android 15 no afecta las pantallas en modo envolvente porque las apps envolventes ya tienen la integración borde a borde.

Cómo proteger las barras del sistema

Tal vez desees que tu app tenga una barra transparente para la navegación por gestos y una barra opaca o traslúcida para la navegación con tres botones.

En Android 15, una navegación con tres botones traslúcida es la configuración predeterminada porque la plataforma configura la propiedad window.isNavigationBarContrastEnforced en true. La navegación por gestos se mantiene transparente.

Una app en navegación con tres botones.

La navegación con tres botones es traslúcida de forma predeterminada.

En general, una navegación con tres botones traslúcida debería ser suficiente. Sin embargo, en algunos casos, la app podría requerir una navegación con tres botones opaca. En primer lugar, configura la propiedad window.isNavigationBarContrastEnforced en false. Luego, usa WindowInsetsCompat.tappableElement para Views o WindowInsets.tappableElement para Compose. Si el valor es 0, el usuario usa la navegación por gestos. De lo contrario, el usuario usa la navegación con tres botones. Si el usuario usa la navegación con tres botones, dibuja una vista o un cuadro detrás de la barra de navegación. Un ejemplo de composición podría ser así:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            window.isNavigationBarContrastEnforced = false
            MyTheme {
                Surface(...) {
                    MyContent(...)
                    ProtectNavigationBar()
                }
            }
        }
    }
}

// Use only if required.
@Composable
fun ProtectNavigationBar(modifier: Modifier = Modifier) {
   val density = LocalDensity.current
   val tappableElement = WindowInsets.tappableElement
   val bottomPixels = tappableElement.getBottom(density)
   val usingTappableBars = remember(bottomPixels) {
       bottomPixels != 0
   }
   val barHeight = remember(bottomPixels) {
       tappableElement.asPaddingValues(density).calculateBottomPadding()
   }

   Column(
       modifier = modifier.fillMaxSize(),
       verticalArrangement = Arrangement.Bottom
   ) {
       if (usingTappableBars) {
           Box(
               modifier = Modifier
                   .background(MaterialTheme.colorScheme.background)
                   .fillMaxWidth()
                   .height(barHeight)
           )
       }
   }
}

Una app en navegación con tres botones.

Navegación con tres botones opaca

6. Revisa el código de la solución

El método onCreate del archivo MainActivity.kt debería verse así:

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       installSplashScreen()
       enableEdgeToEdge()
       window.isNavigationBarContrastEnforced = false
       super.onCreate(savedInstanceState)
       setContent {
           Main(
               shortcutParams = extractShortcutParams(intent),
           )
       }
   }
}

El elemento ChatContent componible dentro del archivo ChatScreen.kt debería controlar inserciones:

private fun ChatContent(...) {
   ...
   Scaffold(...) { innerPadding ->
       Column {
           ...
           InputBar(
               input = input,
               onInputChanged = onInputChanged,
               onSendClick = onSendClick,
               onCameraClick = onCameraClick,
               onPhotoPickerClick = onPhotoPickerClick,
               contentPadding = innerPadding.copy(
                    layoutDirection, top = 0.dp
                ),
               sendEnabled = sendEnabled,
               modifier = Modifier
                   .fillMaxWidth()
                   .windowInsetsPadding(
                       WindowInsets.ime.exclude(WindowInsets.navigationBars)
                    ),
            )
       }
   }
}

El código de la solución está disponible en la rama principal. Si ya descargaste SociaLite:

git checkout main

De lo contrario, puedes volver a descargar el código para ver la rama principal directamente o a través de Git:

git clone git@github.com:android/socialite.git