Temas en Compose con Material 3

1. Introducción

En este codelab, aprenderás a aplicar temas a tus apps en Jetpack Compose con Material Design 3. También aprenderás sobre los componentes básicos de los esquemas de colores, la tipografía y las formas de Material Design 3, que te ayudarán a usar temas para tu aplicación de formas personalizadas y accesibles.

Además, explorarás la compatibilidad con los temas dinámicos, junto con los diferentes niveles de énfasis.

Qué aprenderás

En este codelab, aprenderás lo siguiente:

  • Aspectos clave de los temas de Material 3
  • Esquemas de colores de Material 3 y cómo generar temas para tu app
  • Cómo admitir temas claro, oscuro y dinámicos para tu app
  • Tipografía y formas para personalizar tu app
  • Componentes de Material 3 y cómo personalizar tu app para darle estilo

Qué compilarás

En este codelab, usarás un tema en una app cliente de correo electrónico llamada Reply. Comenzarás con una aplicación sin estilo, usarás el tema del modelo de referencia y, con base en lo que aprendas, aplicarás el tema en la aplicación y agregarás compatibilidad con el tema oscuro.

d15db3dc75a9d00f.png

Punto de partida predeterminado de nuestra app con el tema del modelo de referencia.

Crearás tu tema con el esquema de colores, la tipografía y las formas, y, luego, lo aplicarás a la lista de correos electrónicos y a la página de detalles de la app. También agregarás compatibilidad con el tema dinámico. Al final del codelab, la app admitirá los temas dinámicos y de color.

Material 3 claro

Punto final del codelab de temas con temas de color claro y temas dinámicos claros.

Material 3 oscuro

Punto final del codelab de temas con temas de color oscuro y temas dinámicos oscuros.

Lo que necesitarás

2. Prepárate

En este paso, descargarás el código completo de la app de Reply a la que aplicarás diseño en este codelab.

Obtén el código

El código de este codelab se puede encontrar en el repositorio de GitHub de codelab-android-compose. Para clonarlo, ejecuta lo siguiente:

$ git clone https://github.com/android/codelab-android-compose

Como alternativa, puedes descargar dos archivos ZIP:

Revisa la app de ejemplo

El código que acabas de descargar contiene código para todos los codelabs de Compose disponibles. Para completar este codelab, abre el proyecto ThemingCodelab en Android Studio.

Te recomendamos que comiences con el código de la rama main y sigas el codelab paso a paso a tu propio ritmo. En cualquier momento, puedes ejecutar cualquiera de las versiones en Android Studio cambiando la rama de git del proyecto.

Explora el código de partida

El código principal contiene un paquete de IU que incluye los siguientes paquetes y archivos principales con los que interactuarás:

  • MainActivity.kt: Es la actividad de punto de entrada donde inicias la app de Reply.
  • com.example.reply.ui.theme: Este paquete contiene temas, tipografía y esquemas de colores. Agregarás temas de Material en este paquete.
  • com.example.reply.ui.components: Contiene los componentes personalizados de la app, como los elementos de lista, las barras de la aplicación, etc. Aplicarás temas a estos componentes.
  • ReplyApp.kt: Esta es nuestra función principal de componibilidad en la que comenzará el árbol de IU. En este archivo, aplicarás los temas de nivel superior.

Este codelab se enfocará en los archivos del paquete ui.

3. Temas de Material 3

Jetpack Compose ofrece una implementación de Material Design, un sistema de diseño integral para crear interfaces digitales. Los componentes (botones, tarjetas, interruptores, etc.) de Material Design se basan en los temas de Material, que son una forma sistemática de personalizar Material Design para reflejar mejor la marca de tu producto.

El tema de Material 3 incluye los siguientes subsistemas para agregar temas a tu app: esquema de colores, tipografía y formas. Cuando personalizas estos valores, tus cambios se reflejan automáticamente en los componentes de M3 que usas para compilar la app. Analicemos cada subsistema y, luego, implementemos eso en la app de ejemplo.

Subsistemas de Material Design: color, tipografía y formas.

Subsistemas de colores, tipografía y formas de Material 3.

4. Esquemas de colores

La base de un esquema de colores es el conjunto de cinco colores clave que se relaciona con una paleta tonal de 13 tonos que usan los componentes de Material 3.

Cinco colores clave del modelo de referencia para crear un tema en M3.

Cinco colores clave del modelo de referencia para crear un tema en M3.

Cada color de contraste (primario, secundario y terciario) se proporciona en cuatro colores compatibles de diferentes tonos para crear una vinculación, definir el énfasis y lograr una expresión visual.

Cuatro colores tonales de colores primario, secundario y terciario para los colores del modelo de referencia.

Cuatro colores tonales de colores primario, secundario y terciario para los colores del modelo de referencia.

De manera similar, los colores neutros también se dividen en cuatro tonos compatibles que se usan para las superficies y el fondo. También son importantes para destacar los íconos de texto cuando se colocan en cualquier superficie.

Cuatro colores tonales de colores neutros del modelo de referencia.

Cuatro colores tonales de colores neutros del modelo de referencia.

Obtén más información sobre el esquema de colores y los roles de color.

Cómo generar esquemas de colores

Si bien puedes crear un ColorScheme personalizado de forma manual, suele ser más fácil generar uno con los colores de origen de tu marca. La herramienta Material Theme Builder te permite hacer esto y, de manera opcional, exportar el código de temas de Compose.

Puedes elegir el color que quieras, pero en nuestro caso de uso, usarás el color primario predeterminado para Reply#825500. Haz clic en el color Primary en la sección Core colors y agrega el código en el selector de color.

294f73fc9d2a570e.png

Agregado del código de color primario en Material Theme Builder.

Una vez que agregues el color primario en Material Theme Builder, deberías ver el siguiente tema y la opción de exportar en la esquina superior derecha. En este codelab, exportarás el tema en Jetpack Compose.

Material Theme Builder con la opción de exportar en la esquina superior derecha.

Material Theme Builder con la opción de exportar en la esquina superior derecha.

El color primario #825500 genera el siguiente tema que agregarás a la app. Material 3 proporciona una amplia variedad de roles de color para expresar de manera flexible el estado, la importancia y el énfasis de un componente.

Esquema claro y oscuro de colores exportado a partir del color primario.

Esquema claro y oscuro de colores exportado a partir del color primario.

El archivo generado The Color.kt contiene los colores de tu tema con todos los roles definidos para los colores de los temas claro y oscuro.

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)

val seed = Color(0xFF825500)

El archivo generado The Theme.kt contiene una configuración para el tema de la app y los esquemas de colores claro y oscuro. También contiene la función de componibilidad de temas principal, AppTheme().

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable

private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)

private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

El elemento principal para implementar temas en Jetpack Compose es el elemento componible MaterialTheme.

Une el elemento MaterialTheme() componible en la función AppTheme(), que toma dos parámetros:

  • useDarkTheme: Este parámetro está vinculado a la función isSystemInDarkTheme() para observar la configuración de temas del sistema y aplicar el tema oscuro o el claro. Si quieres mantener tu app con un tema oscuro o claro de forma manual, puedes pasar un valor booleano a useDarkTheme.
  • content: Es el contenido al que se aplicará el tema.

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Si intentas ejecutar la app ahora, deberías notar que se ve igual. Aunque hayas importado nuestro nuevo esquema de colores con nuevos temas, seguirás viendo el tema del modelo de referencia porque no aplicaste el tema a la app de Compose.

App con el tema del modelo de referencia cuando no se aplica ningún tema.

App con el tema del modelo de referencia cuando no se aplica ningún tema.

Para aplicar el tema nuevo, en MainActivity.kt, une el elemento componible principal ReplyApp con la función principal de temas, AppTheme().

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

También actualizarás las funciones de vista previa para ver el tema aplicado a las vistas previas de la app. Une el elemento componible ReplyApp dentro de ReplyAppPreview() con el AppTheme para aplicar temas a las vistas previas.

Los temas claro y oscuro del sistema se definen en los parámetros de vista previa, por lo que verás ambas vistas previas.

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

Si ejecutas la app ahora, deberías obtener vistas previas de la app con los colores de tema importados en lugar del tema del modelo de referencia.

fddf7b9cc99b1fe3.png be7a661b4553167b.png

App con el tema del modelo de referencia (izquierda).

App con el tema de color importado (derecha).

674cec6cc12db6a0.png

Vistas previas clara y oscura de las apps con temas de colores importados.

Material 3 admite los esquemas de colores claro y oscuro. Solo uniste la app con el tema importado. Los componentes de Material 3 usan roles de color predeterminados.

Veamos los roles de color y los usos antes de comenzar a agregarlos a la app.

Roles de color y accesibilidad

Cada función de color se puede usar en varios lugares según el estado, la importancia y el énfasis del componente.

1f184a05ea57aa84.png

Roles de color de colores primarios, secundarios y terciarios.

Primary es el color base, que se utiliza para los componentes principales, como botones importantes y estados activos.

El color de clave Secondary se usa para los componentes menos destacados en la IU, como los chips de filtros.

El color de clave Tertiary se usa para proporcionar contraste, y los colores neutros se usan para el fondo y las superficies de la app.

El sistema de colores de Material ofrece valores de tono y mediciones estándar que se pueden usar para cumplir con las relaciones de contraste accesibles. Usa el color On Primary sobre el color Primary, y el color On Primary Container sobre el color Primary Container, y aplica el mismo criterio para los colores neutros y de contraste para ofrecer una experiencia de contraste accesible al usuario.

Para obtener más información, consulta los roles de color y accesibilidad.

Elevaciones tonales y de sombras

Material 3 representa la elevación, principalmente, usando superposiciones de colores tonales. Esta es una nueva forma de diferenciar contenedores y superficies entre sí (aumentar la elevación tonal utiliza un tono más prominente) además de las sombras.

Elevación tonal con elevación de sombra Elevación tonal en el nivel 2 que toma el color del espacio de color primario.

Las superposiciones de elevación en el tema oscuro también cambiaron por las superposiciones de color tonales en Material Design 3. El color de superposición proviene del espacio de color primario.

La superficie de M3 (el elemento componible de copia de seguridad detrás de la mayoría de los componentes de M3) incluye compatibilidad con la elevación de tonos y sombras:

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

Cómo agregar colores a la app

Si ejecutas la app, puedes ver que los colores exportados se muestran en la app, donde los componentes toman colores predeterminados. Ahora que conocemos los roles de color y su uso, usemos un tema para la app con los roles de color correctos.

be7a661b4553167b.png

App con tema de color y componentes con roles de color predeterminados.

Colores de superficie

En la pantalla principal, comenzarás uniendo el elemento componible principal de la app en una función Surface() para proporcionar la base para que el contenido de la app se coloque encima. Abre MainActivity.kt y une el elemento componible ReplyApp() con Surface.

También proporcionarás una elevación tonal de 5 dp para darle a la superficie un color tonal del espacio principal, lo que ayuda a brindar contraste con el elemento de la lista y la barra de búsqueda ubicada sobre él. De forma predeterminada, la elevación tonal y de sombras para la superficie es de 0 dp.

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

Si ejecutas tu aplicación ahora y consultas las páginas de lista y detalles, deberías ver la superficie tonal aplicada a toda la app.

be7a661b4553167b.png e70d762495173610.png

Fondo de la app sin colores tonal y de superficie (izquierda).

Fondo de la app con colores tonal y de superficie aplicados (derecha).

Colores de la barra de la aplicación

Nuestra barra de búsqueda personalizada en la parte superior no tiene un fondo claro como el diseño solicitado. De forma predeterminada, recurre a la superficie base predeterminada. Puedes proporcionar un fondo para aplicar una separación clara.

5779fc399d8a8187.png

Barra de búsqueda personalizada sin fondo (izquierda).

Barra de búsqueda personalizada con fondo (derecha).

Ahora, editarás ui/components/ReplyAppBars.kt, que contiene la barra de la aplicación. Agregarás MaterialTheme.colorScheme.background al Modifier del elemento componible Row.

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

Ahora deberías ver una separación clara entre la superficie tonal y la barra de la aplicación con color de fondo.

b1b374b801dadc06.png

Barra de búsqueda con color de fondo sobre la superficie tonal.

Colores del botón de acción flotante

70ceac87233fe466.png

BAF grande sin ningún tema aplicado (izquierda).

BAF grande con tema de color terciario aplicado (derecha).

En la pantalla principal, puedes mejorar la apariencia del botón de acción flotante (BAF) para que se destaque como un botón de llamado a la acción. Para implementar esto, le aplicarás un color terciario de contraste.

En el archivo ReplyListContent.kt, actualiza el containerColor del BAF a tertiaryContainer y el color del contenido a onTertiaryContainer para mantener la accesibilidad y el contraste de color.

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/
  }
}

Ejecuta la app para ver tu BAF con tema aplicado. En este codelab, usarás LargeFloatingActionButton.

Colores de tarjetas

La lista de direcciones de correo electrónico que aparece en la pantalla principal utiliza un componente de tarjeta. De forma predeterminada, es una tarjeta rellena que usa el color de la variante de superficie para el color del contenedor para proporcionar una separación clara entre el color de la superficie y el de la tarjeta. Compose también ofrece implementaciones de ElevatedCard y OutlinedCard.

Puedes destacar algunos elementos importantes usando los tonos de color secundario. Para modificar ui/components/ReplyEmailListItem.kt, actualizarás el color del contenedor de la tarjeta con CardDefaults.cardColors() en los correos electrónicos importantes:

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/
}

5818200be0b01583.png 9367d40023db371d.png

Destaca el elemento de la lista con el color secundario del contenedor en la superficie tonal.

Color del elemento de la lista de la página de detalles

Ya aplicaste un tema a la pantalla principal. Haz clic en cualquiera de los elementos de la lista de correos electrónicos para revisar la página de detalles.

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

Página de detalles predeterminada sin un elemento de lista con tema aplicado (izquierda).

Elemento de la lista de la página de detalles con tema de fondo aplicado (derecha).

Como tu elemento de lista no tiene ningún color aplicado, recurre al color de superficie tonal predeterminado. Aplicarás un color de fondo al elemento de la lista para crear una separación y agregarás padding para dejar un espacio alrededor de nuestro fondo.

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

Puedes ver que, con solo proporcionar el fondo, tienes una separación clara entre la superficie tonal y el elemento de la lista.

Ahora tienes las páginas principal y de detalles con roles de color y uso correctos. Veamos cómo tu app puede aprovechar los colores dinámicos para brindar una experiencia aún más personalizada y uniforme.

5. Cómo agregar colores dinámicos en la app

El color dinámico es la parte clave de Material 3, en la que un algoritmo deriva colores personalizados del fondo de pantalla de un usuario para aplicarlos a las IU de sus apps y sistema.

Los temas dinámicos hacen que tus apps sean más personalizadas. También ofrece a los usuarios una experiencia uniforme y fluida con el tema del sistema.

El color dinámico está disponible en Android 12 y versiones posteriores. Si el color dinámico está disponible, puedes configurar un esquema de colores dinámico con dynamicDarkColorScheme() o dynamicLightColorScheme(). De lo contrario, debes volver a usar un ColorScheme oscuro o claro predeterminado.

Reemplaza el código de la función AppTheme en el archivo Theme.kt por el siguiente:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

Tema dinámico tomado del fondo de pantalla de Android 13.

Cuando ejecutes la app ahora, deberías ver los temas dinámicos aplicados con el fondo de pantalla predeterminado de Android 13.

Es posible que también quieras que la barra de estado tenga un estilo dinámico según el esquema de colores que se use para aplicar un tema a tu app.

1095e2b2c1ffdc14.png

App con barra de estado sin color aplicado (izquierda).

App con barra de estado con color aplicado (derecha).

Para actualizar el color de la barra de estado según el color primario de tu tema, agrega el color de la barra de estado después de la selección de esquema de colores en el elemento componible AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

Cuando ejecutes la app, deberías ver la barra de estado con el tema de color primario. También puedes probar el tema dinámico claro y oscuro cambiando el tema oscuro del sistema.

69093b5bce31fd43.png

Temas dinámicos claro (izquierda) y oscuro (derecha) aplicados con el fondo de pantalla predeterminado de Android 13.

Hasta ahora, aplicaste colores a tu app que mejoraron su apariencia. Sin embargo, puedes ver que todo el texto de la app tiene el mismo tamaño, por lo que ahora puedes agregar tipografía a la app.

6. Tipografía

Material Design 3 define una escala de tipo. Los nombres y las agrupaciones se simplificaron a los siguientes: gráficos, encabezados, títulos, cuerpos y etiquetas, con tamaños grandes, medianos y pequeños para cada uno.

999a161dcd9b0ec4.png

Escala de tipo de Material 3.

Cómo definir la tipografía

Compose proporciona la clase Typography de M3, junto con las clases TextStyle y font-related existentes, para modelar la escala de tipo de Material 3.

El constructor de tipografía ofrece valores predeterminados para cada estilo, de modo que puedes omitir cualquier parámetro que no quieras personalizar. Para obtener más información, consulta los estilos de tipografía y sus valores predeterminados.

Usarás cinco estilos de tipografía en tu app: headlineSmall, titleLarge, bodyLarge, bodyMedium y labelMedium. Estos estilos cubrirán la pantalla principal y la pantalla de detalles.

Pantalla que muestra el uso de la tipografía en el estilo del título, la etiqueta y el cuerpo.

Pantalla que muestra el uso de la tipografía en el estilo del título, la etiqueta y el cuerpo.

A continuación, ve al paquete ui/theme y abre Type.kt. Agrega el siguiente código para brindar tu propia implementación para algunos de los estilos de texto, en lugar de los valores predeterminados:

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

Ya se definió la tipografía. Para agregarla a tu tema, pásala al elemento componible MaterialTheme() dentro de AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       content = content
   )
}

Cómo trabajar con la tipografía

Al igual que los colores, accederás al estilo de la tipografía para el tema actual con MaterialTheme.typography. Esto te brinda la instancia de tipografía para usar toda la tipografía definida en Type.kt.

Text(
   text = "Hello M3 theming",
   style = MaterialTheme.typography.titleLarge
)

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

Es probable que tu producto no necesite los 15 estilos predeterminados de la escala de tipo de Material Design. En este codelab, se eligieron cinco tamaños, mientras que el resto se omitió.

Como no aplicaste la tipografía a los elementos componibles Text(), todo el texto recurre a Typography.bodyLarge de forma predeterminada.

Tipografía de lista de la pantalla principal

A continuación, aplica la tipografía a la función ReplyEmailListItem en ui/components/ReplyEmailListItem.kt para crear una distinción entre títulos y etiquetas:

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

Pantalla principal sin tipografía aplicada (izquierda).

Pantalla principal con tipografía aplicada (derecha).

Tipografía de lista de la página de detalles

De manera similar, agregarás la tipografía en la pantalla de detalles actualizando todos los elementos componibles de texto de ReplyEmailThreadItem en ui/components/ReplyEmailThreadItem.kt:

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

Pantalla de detalles sin tipografía aplicada (izquierda).

Pantalla de detalles con tipografía aplicada (derecha).

Cómo personalizar la tipografía

Con Compose, es muy fácil personalizar tu estilo de texto o proporcionar tu fuente personalizada. Puedes modificar TextStyle para personalizar el tipo y la familia de fuentes, el espaciado entre letras, etcétera.

Cambiarás el estilo de texto del archivo theme/Type.kt, que se reflejará en todos los componentes que lo usen.

Actualiza fontWeight a SemiBold y lineHeight a 32.sp para titleLarge, que se usa para el asunto en el elemento de lista. Esto pondrá más énfasis en el asunto y proporcionará separaciones claras.

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

Aplicación de tipografía personalizada en el texto del asunto.

7. Formas

Las superficies de Material se pueden mostrar en diferentes formas. Las formas dirigen la atención, identifican los componentes, comunican el estado y expresan la marca.

Cómo definir formas

Compose proporciona la clase Shapes con parámetros expandidos para implementar nuevas formas de M3. La escala de forma de M3, similar a la escala de tipo, habilita un rango expresivo de formas en toda la IU.

Existen diferentes tamaños de formas en la escala:

  • Extrapequeño
  • Pequeño
  • Mediano
  • Grande
  • Extragrande

De forma predeterminada, cada forma tiene un valor predeterminado que se puede anular. En tu app, usarás la forma mediana para modificar el elemento de lista, pero también puedes declarar otras formas. Crea un archivo nuevo llamado Shape.kt en el paquete ui/theme y agrega el código de las formas:

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

Ahora que definiste tus shapes, pásalas al elemento MaterialTheme de M3 como lo hiciste con los colores y la tipografía:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes,
       content = content
   )
}

Cómo trabajar con formas

Al igual que el color y la tipografía, puedes aplicar formas a componentes de Material con MaterialTheme.shape, que te brinda la instancia de Shape para acceder a formas de Material.

Muchos componentes de Material ya tienen formas predeterminadas aplicadas, pero puedes proporcionar y aplicar tus propias formas a los componentes a través de los espacios disponibles.

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

Valores de formas predeterminados para todos los componentes de Material 3.Asignación de componentes de Material con diferentes tipos de formas.

Puedes ver la asignación de formas para todos los componentes en la documentación de Shape.

Hay otras dos formas disponibles, RectangleShape y CircleShape, que son parte de Compose. La forma rectangular no tiene radio de borde, y la forma circular muestra bordes redondeados en su totalidad.

También puedes aplicar formas a los componentes conModifiers que toman formas, como Modifier.clip, Modifier.background y Modifier.border.

Forma de la barra de la aplicación

Queremos que la barra de la aplicación tenga un fondo con esquinas redondeadas:

f873392abe535494.png

TopAppBar está usando un elemento Row con un color de fondo. Para lograr el fondo con esquinas redondeadas, define la forma del fondo pasando CircleShape al modificador del fondo:

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

Forma del elemento de lista de la página de detalles

En la pantalla principal, estás usando una tarjeta que usa Shape.Medium de forma predeterminada. Sin embargo, para nuestra página de detalles, usaste una columna con color de fondo. Para lograr una apariencia uniforme, aplícale una forma mediana.

3412771e95a45f36.png 80ee881c41a98c2a.png

Columna de elementos de lista de la página de detalles sin forma en el elemento de lista (izquierda) y con forma mediana en el elemento de lista (derecha).

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

Ahora, cuando ejecutas tu app, se muestra un elemento de lista de la pantalla de detalles con forma medium.

8. Énfasis

El énfasis en la IU te ayuda a destacar cierto contenido por sobre el otro, como cuando quieres diferenciar el título de los subtítulos. El énfasis en M3 usa variantes de color y sus combinaciones de colores. Existen dos formas de agregar énfasis:

  1. Puedes usar los colores de tipo Surface, Surface-Variant y Background junto con los colores de tipo On Surface y On Surface-Variant del sistema de colores expandido de M3.

Por ejemplo, el color Surface se puede utilizar con el de On Surface Variant, y el Surface-Variant se puede usar con On Surface para proporcionar diferentes niveles de énfasis.

Las variantes de superficie también se pueden utilizar con colores de contraste para proporcionar menos énfasis que los colores de la variante On y, aun así, mantener la accesibilidad y la relación de contraste.

Roles de color para la superficie, el fondo y las variantes de superficie.

Roles de color para la superficie, el fondo y las variantes de superficie.

  1. Puedes usar diferentes tamaños de fuente para el texto. Como viste en la sección de tipografía, puedes proporcionar tamaños de fuente personalizados a tu escala de tipo para dar un énfasis diferente.

A continuación, actualiza ReplyEmailListItem.kt para ofrecer una diferencia de énfasis usando la variante de superficie. De forma predeterminada, el contenido de la tarjeta toma el color predeterminado según el fondo.

Actualizarás el color de la función de componibilidad del texto del cuerpo y de la marca de tiempo a onSurfaceVariant. De esta manera, se reducirá su énfasis en comparación con onContainerColors, que se aplica a los elementos componibles de texto del asunto y el título de forma predeterminada.

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

Texto del cuerpo y de la marca de tiempo con el mismo énfasis en comparación con el asunto y el título (izquierda).

Texto del cuerpo y de la marca de tiempo con énfasis reducido en comparación con el sujeto y título (derecha).

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant,
   overflow = TextOverflow.Ellipsis
)

Para la tarjeta de correo electrónico importante con secondaryContainer de fondo, el color del texto es el onSecondaryContainer de forma predeterminada. Para los otros correos electrónicos, el fondo tiene el color surfaceVariant,, por lo que todo el texto será de color onSurfaceVariant de forma predeterminada.

9. Felicitaciones

¡Felicitaciones! Completaste correctamente este codelab. Implementaste los temas de Material con Compose usando colores, tipografía y formas junto con colores dinámicos para aplicar temas en tu aplicación y ofrecer una experiencia personalizada.

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

Fin de los temas resultantes con colores dinámicos y tema de color aplicado.

Próximos pasos

Consulta nuestros otros codelabs sobre la ruta de aprendizaje de Compose:

Lecturas adicionales

Apps de ejemplo

Documentos de referencia