Hay varias formas de compilar tus apps con estilos. Lo que elijas dependerá de la posición de tu app en relación con su adopción de Material Design:
- Sistema de diseño totalmente personalizado, sin usar 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.
- Usa Material Design
- Recomendación: Espera la adopción de Material para integrarlo con los estilos. Usa estilos en tus propios componentes cuando sea posible.
La capa de estilo
En el modelo tradicional de Compose, la personalización suele depender en gran medida de anular los tokens globales (colores y tipografía) que proporciona MaterialTheme, o bien de ajustar y anular las propiedades de un elemento componible del sistema de diseño cuando sea posible.
A veces, hay propiedades dentro de la capa de Material que no se exponen a través de los subsistemas o parámetros, pero son valores predeterminados codificados de forma rígida 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) |
| Estilos atómicos | Estilo que realiza exactamente un cambio de propiedad | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| Estilos de componentes | Configuraciones específicas de los componentes | Un botón con fondo primario y relleno de 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| Componentes | El elemento de la IU funcional que consume un estilo. | Button(style = buttonStyle) { ... } |
Estilos atómicos frente a monolíticos
Con la API de Styles, puedes dividir un estilo en estilos atómicos separados.
En lugar de definir estilos complejos y específicos de los componentes, como baseButtonStyle, también puedes crear estilos de utilidad pequeños y 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 potentes funciones 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ómico):
// 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 estilos
Considera cuándo: Te entregaron una guía de marca extensa que no está basada en Material Design y no planeas usar Material Design.
Estrategia: Implementa un sistema de diseño totalmente 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 de tu sistema de diseño. Omites MaterialTheme por completo para las definiciones visuales y
ya creaste tu propio tema personalizado. Compilas un CompanyTheme que actúa como un contenedor para tus estilos.
- Cómo funciona: Crea un objeto
CompanyThemeque contenga objetosStylepara cada componente de tu sistema. Tus componentes (ya sean wrappers 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 estilo: Los estilos son la definición principal de tu sistema de diseño. Los tokens son variables con nombre que se introducen en 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 compilas 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 base desde cualquier lugar de tu proyecto.
Crea una clase
Stylesque almacene los distintos estilos de tu aplicación y cree 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 de temas globales, existen estrategias alternativas para incorporar Styles en tus apps. Puedes aprovechar Styles en línea para sitios de llamada específicos o usar definiciones estáticas cuando no sean necesarias las capacidades completas de temas.
Styles no se debe intercambiar de forma condicional, a menos que todo el estilo sea fundamentalmente diferente. Debes preferir acceder a tokens dinámicos dentro de una definición visual en lugar de cambiar entre objetos de estilo distintos.