Jetpack Compose está cambiando el paradigma del desarrollo nativo de aplicaciones y nos introduce en las interfaces declarativas. En este post ya hablamos sobre los estados y recomposición en Jetpack Compose y aquí os contamos cómo diseñar interfaces. Es momento ahora de hablar del elefante en la habitación: los temas (Themes).

Seamos sinceros: todo aquel que lleve programando en Android el tiempo suficiente sabe que cuando se desarrolló la parte de los Styles y en especial los Themes solo Dios y el programador que lo hizo sabían cómo funcionaban… Y a día de hoy solo Dios lo sabe.

Pero no hablemos del pasado, porque ha llegado Compose y sus Themes y no podemos pasar la oportunidad de enterarnos de cómo funcionan y educarlos cuando aún son jóvenes para evitar futuros disgustos.

En este post hablaremos sobre cómo trabajar con los Themes de Material3: las partes que lo componen, su efecto y cómo personalizar sus valores.
MaterialTheme

MaterialTheme

Hay que dejar claro que Jetpack Compose nos permite utilizar cualquier Theme propio que queramos definir (hablaremos sobre esto en un próximo post), pero aun así, si queremos, podemos utilizar un diseño ya probado y adaptado a dispositivos Android: Material Design y, en concreto, sus librerías para Compose Material3. Este sistema de diseño pertenece a Google y se adapta a todas las situaciones y tamaños de pantalla.

Esta opción es la más rápida y cómoda cuando no llevamos mucho tiempo diseñando con Compose, ya que aparte de ser muy completa, tiene una gran comunidad de desarrollo detrás para solucionar los problemas. Conforme nos familiaricemos con este nuevo sistema de estilado de aplicaciones, podremos ir ampliándolo e incluso sustituyéndolo por uno propio.

Anatomía

Cuando creamos un nuevo proyecto de cero, Android Studio ya crea una estructura como la siguiente dentro de la carpeta ui:

Carpeta ui.

Vamos a ver un poco más en detalle la función de cada una de las partes:

Color

Empecemos por la clase más sencilla de todas, ya que solo contiene una definición de colores en hexadecimal (o RGB si se prefiere) con sus nombres:

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

El tema de Material asigna colores a una serie de variables por defecto basadas en los roles de dichos colores, como son primary, onPrimary, background, etc. En la siguiente imagen podemos ver todos los colores/roles definidos por el tema de MaterialTheme:

Colores/roles definidos por el tema de MaterialTheme:

Material define estos valores en su clase ColorScheme, la cual tiene una constructora y dos funciones muy importantes: lightColorsScheme y darkColorsScheme.

Si las analizamos, veremos que son simplemente una asignación de colores puros (definiciones hexadecimales o ARGB) con los alias definidos por Material3. Con ello, se definirá una paleta de colores para cada modo de visualización: light o dark. Más adelante, veremos cómo hacer uso de ellas.

Empezaremos viendo cómo actúan estas variables predefinidas a la hora de diseñar nuestro Theme y más adelante abordaremos cómo añadir nuevas variables al tema de Material o incluso crearemos nuestras propias definiciones. Por ahora, nos interesa saber que podemos sobrescribir cualquiera de los valores predefinidos por MaterialTheme para que utilice otro valor.

Shapes (Formas)

Como ya hemos visto en la sección anterior, Material define una serie de roles para los colores. Además de ello, también define tres tipos de forma para sus componentes (como Dialogs o Snackbars, por ejemplo): small/medium/large.

val Shape = Shapes(
   small = RoundedCornerShape(
       all = 12.dp
   ),
   medium = CutCornerShape(16.dp)
)

Podemos obtener más información acerca de estas 3 categorías aquí.

Todas las categorías tienen asignado por defecto una forma con bordes redondeados, tal y como se muestra en todos los componentes de Material3. En el ejemplo de código anterior redefinimos 2 de dichas categorías de forma que actualizamos el grosor del borde e incluso el tipo del mismo (CutCornerShape).

Typography (tipografía)

Por último, tenemos la clase que define las tipografías por defecto, las cuales vienen a ser simplemente definiciones de TextStyle que Material pone a nuestra disposición de manera predeterminada. Compose utiliza algunos de los estilos definidos por defecto en los lugares donde las guías de estilo así lo definen.

Las fuentes se agrupan en distintos tipos dentro de Material3:

A continuación podemos ver una representación de los estilos definidos por la clase Typography y sus diferentes valores:

Clase Typography y sus diferentes valores

El constructor de esta clase Typography nos permite sobrescribir cualquiera de los estilos por defecto con nuestro propio TextStyle. Un ejemplo de esto se puede ver en la clase que Android Studio genera automáticamente al crear un nuevo proyecto:

val Typography = Typography(
   bodyLarge = TextStyle(
       fontFamily = FontFamily.Default,
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.5.sp
   )

Este archivo contiene un ejemplo de personalización muy básica de las tipografías, mostrando así el patrón para que podamos modificar toda la clase a nuestro gusto.

Theme

Por último, llegamos a la piedra angular de todo el sistema de estilos: el Theme. Es esta clase la que hará que nuestros valores sobreescritos se utilicen dentro de MaterialTheme. Para entender esto con más detalle, lo primero que deberíamos ver es cómo se usa el tema de Material (o cualquier tema que hayamos definido).

Para aplicar los estilos de nuestro tema, lo único que debemos hacer es rodear el código al que queramos aplicarlo con el Composable MaterialTheme:

MaterialTheme {
   Greeting("Android")
}

De esta forma estaríamos usando el estilo por defecto, pero MaterialTheme acepta una serie de argumentos para configurar qué valores utilizar para los colores, tipografía, etc.:

@Composable
fun MaterialTheme(
   colorScheme: ColorScheme = MaterialTheme.colorScheme,
   shapes: Shapes = MaterialTheme.shapes,
   typography: Typography = MaterialTheme.typography,
   content: @Composable () -> Unit)

Esto quiere decir que podemos invocar a MaterialTheme pasándole como parámetro redefiniciones personalizadas de las clases que espera (Typoraphy o ColorScheme).

El único problema aquí es lo farragoso que puede terminar resultando llamar siempre a MaterialTheme con nuestras redefiniciones en lugar de que se apliquen por defecto. Y es aquí donde entra en juego la última pieza del estilado básico con Material3: nuestro tema custom.

CustomTheme

El uso de este tema personalizado es tan sencillo como crear una función @Composable dentro del archivo Theme, llamada MyCustomTheme:

@Composable
fun MyCustomTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val colorScheme = when {
       darkTheme -> DarkColorScheme
       else -> LightColorScheme
   }
   MaterialTheme(
       colorScheme = colorScheme,
       typography = Typography, // Redefinida en Type.kt
       content = content
   )
}

Podemos definir si queremos que se use el tema claro u oscuro (por defecto, coge la configuración en la que esté el sistema). En función del tema, elige una paleta de colores, y luego llama a MaterialTheme como ya hemos visto.

Es también en este archivo de Themes donde tenemos redefinidas las paletas de colors lightColorScheme y darkColorScheme:

private val DarkColorScheme = darkColorScheme(
   primary = Purple80,
   secondary = PurpleGrey80,
   tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
   primary = Purple40,
   secondary = PurpleGrey40,
   tertiary = Pink40
)

En este caso, estamos cogiendo los colores por defecto y sustituyendo solo algunos: primary, secondary y tertiary.

Ahora solo tenemos que rodear la función Composable que queramos con nuestro MyCustomTheme, tal y como hacíamos con MaterialTheme, y se aplicarán todas nuestras modificaciones de colores, tipografías o formas.

Conclusión

Ahora ya sabemos cómo funcionan los nuevos temas y los estilos en Compose. De momento, podemos dar estilos a una aplicación en Compose de forma bastante completa y adaptándonos al patrón de Material3. Pero la cosa no acaba aquí. Theme tiene un montón de opciones de personalización y extensión para sus temas, los cuales veremos más adelante.

¡Espero que os haya sido de ayuda!

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.