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:
SociaLite con navegación con tres botones | SociaLite con navegación por gestos |
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
- 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
- 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:
Navegación con tres botones | Navegación por gestos |
Pantalla grande |
- 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 |
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.
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
- 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
...
}
...
}
- 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 |
- 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 |
Cómo corregir SociaLite
Para quitar la protección en segundo plano predeterminada de la navegación con tres botones, sigue estos pasos:
- 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.
- 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.
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. 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:
- Navega al archivo
ui/chat/ChatScreen.kt
y, luego, busca el elementoChatContent
componible alrededor de la línea 178, que contiene la IU de la pantalla de conversación.ChatContent
aprovechaScaffold
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 deScaffold
(el parámetroinnerPadding
). Agrega relleno coninnerPadding
deScaffold
enInputBar
. - Busca
InputBar
dentro deChatContent
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:
InputBar
toma un contentPadding
y lo aplica como relleno en el elemento componible Row 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
}
}
}
}
- Vuelve al
InputBar
dentro deChatContent
y cambiacontentPadding
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.
...
)
- Vuelve a ejecutar la app en tu dispositivo con Android 15.
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.
- Para corregir el relleno de la parte superior, crea una copia de
innerPadding
PaddingValues
, configura el relleno de la parte superior en0.dp
y pasa la copia modificada acontentPadding
.
InputBar(
...
contentPadding = innerPadding.copy(layoutDirection, top = 0.dp), //Add this line.
// contentPadding = innerPadding, // Remove this line.
...
)
- Vuelve a ejecutar la app en tu dispositivo con Android 15.
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:
- Usar componentes de Material Design 3 (
androidx.compose.material3
), comoTopAppBar
,BottomAppBar
yNavigationBar
, porque estos aplican automáticamente las inserciones. - Si, en cambio, tu app usa componente de Material 2 (
androidx.compose.material
) en Compose, estos no controlan las inserciones por su cuenta y de forma automática. Sin embargo, puedes obtener acceso a las inserciones y aplicarlas manualmente. Enandroidx.compose.material 1.6.0
y versiones superiores, usa el parámetrowindowInsets
para aplicar las inserciones de forma manual paraBottomAppBar
,TopAppBar
,BottomNavigation
yNavigationRail
. De la misma manera, usa el parámetrocontentWindowInsets
paraScaffold
. De lo contrario, aplica las inserciones de forma manual como relleno. - Si tu app usa componentes de Views y Material (
com.google.android.material
), la mayoría de los componentes de Material basados en Views (comoBottomNavigationView
,BottomAppBar
,NavigationRailView
yNavigationView
) controlan las inserciones, así que tal vez no se requiera trabajo adicional. Sin embargo, tendrás que agregarandroid:fitsSystemWindows="true"
si se usaAppBarLayout
. - Si tu app usa Views y
BottomSheet
,SideSheet
o contenedores personalizados, aplica relleno conViewCompat.setOnApplyWindowInsetsListener
. ParaRecyclerView
, aplica relleno con este objeto de escucha y también agregaclipToPadding="false"
. - Usa
Scaffold
(oNavigationSuiteScaffold
oListDetailPaneScaffold
), en lugar deSurface
, para una IU compleja.Scaffold
te permite colocarTopAppBar
,BottomAppBar
,NavigationBar
yNavigationRail
con facilidad.
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.
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
}
}
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.
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.
Según tu app, es posible que quieras controlar las inserciones aquí.
Para hacerlo en SociaLite, sigue estos pasos:
- En el archivo
ui/ContactRow.kt
, busca el elemento componible Row. - 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í:
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.
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.
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)
)
}
}
}
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