Existen varias formas de compilar tus apps con los estilos. Lo que elijas dependerá de la posición de tu app en relación con la adopción de Material Design:
- Sistema de diseño completamente personalizado que no usa Material Design
- Recomendación: Define estilos de componentes que consuman valores del tema y expongan parámetros de estilo en los componentes del sistema de diseño.
- Cómo usar Material Design
- Recomendación: Espera a que se adopte Material para integrar con Styles. Usa estilos en tus propios componentes siempre que sea posible.
La capa de diseño
En el modelo tradicional de Compose, la personalización suele depender en gran medida de la anulación de tokens globales (colores y tipografía) proporcionados por MaterialTheme, o bien de la anulación y el ajuste de propiedades de un elemento componible del sistema de diseño siempre que sea posible.
A veces, hay propiedades dentro de la capa Material que no se exponen a través de los subsistemas o parámetros, sino que son valores predeterminados codificados en el componente.
Con la API de Styles, hay una nueva capa de abstracción que es un puente entre los subsistemas y los componentes: Styles.
| Capa | Responsabilidad | Ejemplo |
|---|---|---|
| Valores del subsistema | Valores con nombre | val Primary = Color(0xFF34A85E) |
| Atomic Styles | Estilo que realiza exactamente un cambio de propiedad | val largeSizeAtomic = Style { size(100.dp, 40.dp) } |
| Estilos de componentes | Configuraciones específicas del componente | Un botón con fondo principal y padding de 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| Componentes | Es el elemento de la IU funcional que consume un diseño. | Button(style = buttonStyle) { ... } |
Comparación entre los estilos atómicos y los monolíticos
Con la API de Styles, puedes desglosar un diseño en diseños atómicos separados.
En lugar de definir estilos complejos y específicos de los componentes, como baseButtonStyle, también puedes crear pequeños estilos de utilidad de un solo propósito. Estos actúan como tus "átomos".
// Define single-purpose "atomic" styles val paddingAtomic = Style { contentPadding(16.dp) } val roundedCornerShapeAtomic = Style { shape(RoundedCornerShape(8.dp)) } val primaryBackgroundAtomic = Style { background(Color.Blue) } val largeSizeAtomic = Style { size(100.dp, 40.dp) } val interactiveShadowAtomic = Style { hovered { animate { dropShadow( Shadow( offset = DpOffset( 0.dp, 0.dp ), radius = 2.dp, spread = 0.dp, color = Color.Blue, ) ) } } }
Composición con "then"
Una de las funciones potentes de la nueva API de Styles es el operador then, que te permite combinar varios objetos Style. Esto te permite compilar un componente con clases de utilidad atómicas.
Tradicional (no atómica):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Refactorización atómica:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
Adopta estilos en tu sistema de diseño
Ten en cuenta las siguientes opciones cuando adoptes estilos en tu sistema de diseño, según la posición de tu sistema de diseño en el espectro.
Sistema de diseño personalizado con Styles
Consideraciones: Recibiste una guía de marca extensa que no se basa en Material Design y no planeas usar Material Design.
Estrategia: Implementa un sistema de diseño completamente personalizado y expón los estilos como parte del tema.
Esta opción es la ruta de acceso personalizada si no usas Material como el idioma principal del sistema de diseño. Omitiste MaterialTheme por completo para las definiciones visuales y ya creaste tu propio tema personalizado. Compilas un CompanyTheme que actúa como contenedor para tus objetos Styles.
- Cómo funciona: Crea un objeto
CompanyThemeque contenga objetosStylepara cada componente de tu sistema. Tus componentes (ya sean contenedores alrededor de la lógica de Material o implementaciones personalizadas deBoxoLayout) consumen estos estilos directamente y exponen un parámetroStylepara los consumidores de tu sistema de diseño. - La capa de diseño: Los diseños son la definición principal de tu sistema de diseño. Los tokens son variables con nombre que se incorporan a estos estilos. Esto permite una personalización profunda, como definir animaciones únicas para los cambios de estado (por ejemplo, animar la escala y el color al presionar).
Si estás creando tu propio tema personalizado sin usar Material y quieres adoptar estilos, agrega tu lista de estilos a tu tema. Esto te permite acceder a tus estilos básicos desde cualquier lugar de tu proyecto.
Crea una clase
Stylesque almacene los distintos diseños de tu aplicación y crea los valores predeterminados. Por ejemplo, en la app de Jetsnack, la clase se llamaJetsnackStyles:object JetsnackStyles{ val buttonStyle: Style = Style { shape(shapes.medium) background(colors.brand) contentColor(colors.textPrimary) contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) textStyle(typography.labelLarge) disabled { animate { background(colors.brandSecondary) } } } val cardStyle: Style = Style { shape(shapes.medium) background(colors.uiBackground) contentColor(colors.textPrimary) } }
Proporciona
Stylescomo parte de tu tema general y expón funciones de extensión auxiliares enStyleScopepara acceder a los subsistemas:@Immutable class JetsnackTheme( val colors: JetsnackColors = LightJetsnackColors, val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(), val shapes: Shapes = Shapes() ) { companion object { val colors: JetsnackColors @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors val typography: androidx.compose.material3.Typography @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.typography val shapes: Shapes @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes val styles: JetsnackStyles = JetsnackStyles val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme> get() = LocalJetsnackThemeInstance } } val StyleScope.colors: JetsnackColors get() = LocalJetsnackTheme.currentValue.colors val StyleScope.typography: androidx.compose.material3.Typography get() = LocalJetsnackTheme.currentValue.typography val StyleScope.shapes: Shapes get() = LocalJetsnackTheme.currentValue.shapes internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors val theme = JetsnackTheme(colors = colors) CompositionLocalProvider( LocalJetsnackTheme provides theme, ) { MaterialTheme( typography = LocalJetsnackTheme.current.typography, shapes = LocalJetsnackTheme.current.shapes, content = content, ) } }
Accede a
JetsnackStylesdentro de tu elemento componible:@Composable fun CustomButton(modifier: Modifier, style: Style = Style, text: String) { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } // Apply style to top level container in combination with incoming style from parameter. Box(modifier = modifier .clickable( interactionSource = interactionSource, indication = null, enabled = true, role = Role.Button, onClick = { }, ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) { Text(text) } }
Además de la adopción del tema global, existen estrategias alternativas para incorporar Styles en tus apps. Puedes aprovechar Styles intercalado para sitios de llamadas específicos o usar definiciones estáticas cuando no se necesitan capacidades de temas completas.
Styles no se debe intercambiar de forma condicional, a menos que todo el diseño sea fundamentalmente diferente. Te recomendamos que accedas a los tokens dinámicos dentro de una definición visual en lugar de cambiar entre objetos de estilo distintos.