Jetpack Compose ofrece una implementación de Material Design, un sistema de diseño integral para crear interfaces digitales. Los componentes de Material Design (botones, tarjetas, interruptores, etc.) se compilan sobre la aplicación de temas de Material, que es una forma sistemática de personalizar Material Design para que refleje mejor la marca de tu producto. Un tema de Material comprende atributos de color, tipografía y forma. Cuando personalizas esos atributos, tus cambios se reflejan automáticamente en los componentes que usas para compilar tu app.
Jetpack Compose implementa esos conceptos con el elemento MaterialTheme
que admite composición:
MaterialTheme( colors = // ... typography = // ... shapes = // ... ) { // app content }
Configura los parámetros que pasas a MaterialTheme
para aplicar un tema a tu aplicación.
Figura 1: La primera captura de pantalla muestra una app que no configura MaterialTheme
y, por lo tanto, usa el estilo predeterminado. La segunda, muestra una app que pasa parámetros a MaterialTheme
para personalizar el estilo.
Color
Los colores se modelan en Compose con la clase Color
, una clase simple que retiene datos.
val Red = Color(0xffff0000) val Blue = Color(red = 0f, green = 0f, blue = 1f)
Si bien puedes organizarlos como te guste (como constantes de nivel superior, dentro de un singleton o intercalado definido), te recomendamos que especifiques los colores en tu tema y los recuperes desde allí. Este enfoque permite admitir, con facilidad, el tema oscuro y los temas anidados.
Figura 2: Sistema de colores de Material.
Compose proporciona la clase Colors
para modelar el sistema de colores de Material. Colors
ofrece funciones de compilador para crear conjuntos de colores claros u oscuros:
private val Yellow200 = Color(0xffffeb46) private val Blue200 = Color(0xff91a4fc) // ... private val DarkColors = darkColors( primary = Yellow200, secondary = Blue200, // ... ) private val LightColors = lightColors( primary = Yellow500, primaryVariant = Yellow400, secondary = Blue700, // ... )
Una vez que hayas definido tus Colors
, puedes pasarlos a un MaterialTheme
:
MaterialTheme( colors = if (darkTheme) DarkColors else LightColors ) { // app content }
Cómo usar colores de tema
Puedes recuperar los Colors
proporcionados al elemento que admite compilación de MaterialTheme
con MaterialTheme.colors
.
Text( text = "Hello theming", color = MaterialTheme.colors.primary )
Color de contenido y superficie
Muchos componentes aceptan un par de color y color del contenido:
Surface( color = MaterialTheme.colors.surface, contentColor = contentColorFor(color), // ... ) { /* ... */ } TopAppBar( backgroundColor = MaterialTheme.colors.primarySurface, contentColor = contentColorFor(backgroundColor), // ... ) { /* ... */ }
Esto te permite no solo definir el color de un elemento que admite composición, sino también proporcionar un color predeterminado para su contenido, es decir, los elementos que admiten composición que este contiene. Muchos elementos que admiten composición usan este color de contenido de forma predeterminada. Por ejemplo, Text
basa su color en el color de contenido del elemento superior, y Icon
usa ese color para establecer su tono.
Figura 3: Cuando se establecen diferentes colores de fondo, se producen distintos colores de texto y de ícono.
El método contentColorFor()
recupera el color adecuado para mostrar "arriba" de cualquier color del tema. Por ejemplo, si configuras un color de fondo primary
en Surface
, se usará esta función para establecer onPrimary
como color de contenido. Si configuras un color de fondo que no pertenece a ningún tema, también debes especificar un color de contenido apropiado. Usa LocalContentColor
a fin de recuperar el color de contenido preferido para el fondo actual, en una posición determinada en la jerarquía.
Versión alfa de contenido
Muchas veces quieres variar la forma en que destacas el contenido para comunicar la importancia y ofrecer una jerarquía visual. En las recomendaciones de legibilidad de texto de Material Design, se recomienda usar diferentes niveles de opacidad para transmitir diferentes niveles de importancia.
En Jetpack Compose, esto se implementa mediante LocalContentAlpha
.
Puedes especificar una versión alfa de contenido para establecer una jerarquía proporcionando un valor para este objeto CompositionLocal
.
Los elementos anidados que admiten composición pueden usar este valor para aplicar el tratamiento alfa a su contenido.
Por ejemplo, Text
y Icon
utilizan la combinación de LocalContentColor
configurada como LocalContentAlpha
de forma predeterminada. Material especifica algunos valores alfa estándar (high
, medium
y disabled
), que están modelados por el objeto ContentAlpha
.
// By default, both Icon & Text use the combination of LocalContentColor & // LocalContentAlpha. De-emphasize content by setting content alpha CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text( // ... ) } CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { Icon( // ... ) Text( // ... ) }
Para obtener más información sobre CompositionLocal
, consulta la Guía de datos de permisos local con CompositionLocal.
Figura 4: Aplica diferentes niveles de énfasis en el texto para comunicar visualmente la jerarquía de la información. La primera línea de texto es el título, incluye la información más importante y, por lo tanto, usa ContentAlpha.high
. La segunda línea incluye metadatos menos importantes y, por lo tanto, usa ContentAlpha.medium
.
Tema oscuro
En Compose, debes proporcionar diferentes conjuntos de Colors
al elemento MaterialTheme
que admite composición para implementar temas claros y oscuros:
@Composable fun MyTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkColors else LightColors, /*...*/ content = content ) }
En este ejemplo, MaterialTheme
se une a su propia función que admite composición y que acepta un parámetro que especifica si se debe usar un tema oscuro o no. En este caso, la función consulta a la configuración de tema del dispositivo para obtener el valor predeterminado de darkTheme
.
Puedes usar código como el siguiente para verificar si los objetos Colors
actuales son claros u oscuros:
val isLightTheme = MaterialTheme.colors.isLight Icon( painterResource( id = if (isLightTheme) { R.drawable.ic_sun_24 } else { R.drawable.ic_moon_24 } ), contentDescription = "Theme" )
Superposiciones de elevación
En Material, las superficies en temas oscuros con mayor elevación reciben superposiciones de elevación, lo que aclara su fondo. Cuanto más alta sea la elevación de una superficie (se acerque más a una fuente de luz implícita), más se aclarará la superficie.
A esas superposiciones las aplica, automáticamente, el elemento Surface
que admite composición cuando se usan colores oscuros y para cualquier otro elemento de Material que admite composición y que use una superficie:
Surface( elevation = 2.dp, color = MaterialTheme.colors.surface, // color will be adjusted for elevation /*...*/ ) { /*...*/ }
Figura 5: Las tarjetas y la navegación inferior usan el color surface
en el fondo. Como las tarjetas y la navegación inferior se encuentran en diferentes niveles de elevación sobre el fondo, tienen colores un poco diferentes: las tarjetas son más claras que el fondo, y la navegación inferior es más clara que las tarjetas.
Para situaciones personalizadas que no impliquen un elemento Surface
, usa LocalElevationOverlay
, un objeto CompositionLocal
que contiene el elemento ElevationOverlay
que usan los componentes Surface
:
// Elevation overlays // Implemented in Surface (and any components that use it) val color = MaterialTheme.colors.surface val elevation = 4.dp val overlaidColor = LocalElevationOverlay.current?.apply( color, elevation )
Para inhabilitar las superposiciones de elevación, brinda null
en el punto deseado de una jerarquía que admite composición:
MyTheme { CompositionLocalProvider(LocalElevationOverlay provides null) { // Content without elevation overlays } }
Acentuación de color limitada
En la mayoría de los casos, para Material, te recomendamos que apliques acentuación de color limitada para temas oscuros y le des preferencia al color surface
por sobre el color primary
. Los elementos de Material que admiten composición, como TopAppBar
y BottomNavigation
, implementan este comportamiento de forma predeterminada.
Figura 6: Tema oscuro de Material con acentuación de color limitada. La barra superior de la app usa el color principal en el tema claro y el color de superficie en el tema oscuro.
Para situaciones personalizadas, usa la propiedad de extensión primarySurface
:
Surface( // Switches between primary in light theme and surface in dark theme color = MaterialTheme.colors.primarySurface, /*...*/ ) { /*...*/ }
Tipografía
Material define un sistema de tipos y te recomienda que uses una pequeña cantidad de estilos con nombres semánticos.
Figura 7: Sistema de tipos de Material.
Compose implementa el sistema de tipos con Typography
, TextStyle
y clases relacionadas con la fuente. El constructor Typography
ofrece valores predeterminados para cada estilo, de modo que puedes omitir cualquiera que no quieras personalizar:
val raleway = FontFamily( Font(R.font.raleway_regular), Font(R.font.raleway_medium, FontWeight.W500), Font(R.font.raleway_semibold, FontWeight.SemiBold) ) val myTypography = Typography( h1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W300, fontSize = 96.sp ), body1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W600, fontSize = 16.sp ) /*...*/ ) MaterialTheme(typography = myTypography, /*...*/) { /*...*/ }
Si deseas usar siempre la misma fuente, especifica defaultFontFamily parameter
y omite el objeto fontFamily
de cualquier elemento TextStyle
:
val typography = Typography(defaultFontFamily = raleway) MaterialTheme(typography = typography, /*...*/) { /*...*/ }
Cómo usar estilos de texto
Se puede acceder a los objetos TextStyle
a través de MaterialTheme.typography
. Recupera los TextStyle
de la siguiente manera:
Text( text = "Subtitle2 styled", style = MaterialTheme.typography.subtitle2 )
Figura 8: Usa una selección de tipos de letra y estilos para expresar tu marca.
Forma
Material define un sistema de formas, lo que te permite definir formas para componentes grandes, medianos y pequeños.
Figura 9: Sistema de formas de Material.
Compose implementa el sistema de formas con la clase Shapes
, que te permite especificar una CornerBasedShape
para cada categoría de tamaño:
val shapes = Shapes( small = RoundedCornerShape(percent = 50), medium = RoundedCornerShape(0f), large = CutCornerShape( topStart = 16.dp, topEnd = 0.dp, bottomEnd = 0.dp, bottomStart = 16.dp ) ) MaterialTheme(shapes = shapes, /*...*/) { /*...*/ }
Muchos componentes usan estas formas de manera predeterminada. Por ejemplo, Button
, TextField
y FloatingActionButton
, de manera predeterminada, adoptan el valor pequeño, AlertDialog
adopta el valor mediano y ModalDrawer
el grande. Consulta la referencia del esquema de formas para ver la asignación completa.
Cómo usar formas
Se puede acceder a los objetos Shape
a través de MaterialTheme.shapes
. Recupera los Shape
con un código como este:
Surface( shape = MaterialTheme.shapes.medium, /*...*/ ) { /*...*/ }
Figura 10: Usa formas para expresar la marca o el estado.
Estilos predeterminados
En Compose, no existe un concepto equivalente a estilos predeterminados de objetos View de Android. Para brindar una funcionalidad similar, puedes crear tus propias funciones de "sobrecarga" que admitan composición y que unan componentes de Material. Por ejemplo, para crear un estilo de botón, une un botón a tu propia función que admite composición, configura directamente los parámetros que deseas modificar y expón otros como parámetros al elemento que admite composición que esta contiene.
@Composable fun MyButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.secondary ), onClick = onClick, modifier = modifier, content = content ) }
Superposiciones de temas
Para alcanzar el equivalente a superposiciones de temas de objetos View de Android en Compose, puedes anidar elementos MaterialTheme
que admitan composición. Como MaterialTheme
establece, de forma predeterminada, los colores, la tipografía y las formas en el valor de tema actual, si un tema solo establece uno de esos parámetros, los otros mantienen sus valores predeterminados.
Además, cuando migres pantallas basadas en View a Compose, presta atención a los usos del atributo android:theme
. Es probable que necesites un nuevo MaterialTheme
en esa parte del árbol de IU de Compose.
En este ejemplo, la pantalla de detalles usa un PinkTheme
para la mayor parte de la pantalla y, luego, un BlueTheme
para la sección relacionada. Consulta la captura de pantalla y el código que aparecen más adelante.
Figura 11: Temas anidados.
@Composable fun DetailsScreen(/* ... */) { PinkTheme { // other content RelatedSection() } } @Composable fun RelatedSection(/* ... */) { BlueTheme { // content } }
Estados de componentes
Los componentes de Material con los que se puede interactuar (con un clic o un botón de activación, etc.) pueden estar en diferentes estados visuales. Los estados incluyen habilitado, inhabilitado, presionado, etc.
Con frecuencia, los elementos que admiten composición tienen un parámetro enabled
. Si se establece en false
, se evita la interacción, y se cambian propiedades, como el color y la elevación, para transmitir, de forma visual, el estado del componente.
Figura 12: Botón con enabled = true
(izquierda) y enabled = false
(derecha)
En la mayoría de los casos, puedes confiar en los parámetros predeterminados para valores como el color y la elevación. Si deseas configurar los valores que se usan en diferentes estados, hay clases y funciones prácticas disponibles. Consulta el siguiente ejemplo de botón:
Button( onClick = { /* ... */ }, enabled = true, // Custom colors for different states colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.secondary, disabledBackgroundColor = MaterialTheme.colors.onBackground .copy(alpha = 0.2f) .compositeOver(MaterialTheme.colors.background) // Also contentColor and disabledContentColor ), // Custom elevation for different states elevation = ButtonDefaults.elevation( defaultElevation = 8.dp, disabledElevation = 2.dp, // Also pressedElevation ) ) { /* ... */ }
Figura 13: Botón con enabled = true
(izquierda) y enabled = false
(derecha), con valores de color y elevación ajustados.
Ecos
Los componentes de Material usan ondas para indicar con qué se está interactuando. Si usas MaterialTheme
en la jerarquía, se usará Ripple
como un objeto Indication
predeterminado dentro de los modificadores, por ejemplo, clickable
y indication
.
En la mayoría de los casos, puedes confiar en el objeto Ripple
predeterminado. Si deseas configurar su apariencia, puedes usar RippleTheme
para cambiar propiedades como color y alfa.
Puedes extender RippleTheme
y aprovechar las funciones de utilidad defaultRippleColor
y defaultRippleAlpha
. Luego, puedes brindar el tema de ondas personalizado en la jerarquía mediante LocalRippleTheme
:
@Composable fun MyApp() { MaterialTheme { CompositionLocalProvider( LocalRippleTheme provides SecondaryRippleTheme ) { // App content } } } @Immutable private object SecondaryRippleTheme : RippleTheme { @Composable override fun defaultColor() = RippleTheme.defaultRippleColor( contentColor = MaterialTheme.colors.secondary, lightTheme = MaterialTheme.colors.isLight ) @Composable override fun rippleAlpha() = RippleTheme.defaultRippleAlpha( contentColor = MaterialTheme.colors.secondary, lightTheme = MaterialTheme.colors.isLight ) }
Figura 14: Botones con diferentes valores de ondas que se brindan a través de RippleTheme
Más información
Para obtener más información sobre los temas de Material en Compose, consulta los siguientes recursos adicionales.
Codelabs
Videos
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Sistemas de diseño personalizado en Compose
- Cómo migrar de Material 2 a Material 3 en Compose
- Accesibilidad en Compose