Há várias maneiras de criar apps usando estilos. A escolha depende da posição do app em relação à adoção do Material Design:
- Sistema de design totalmente personalizado, sem usar o Material Design
- Recomendação: defina estilos de componentes que consomem valores do tema e exponha parâmetros de estilo em componentes do sistema de design.
- Usando o Material Design
- Recomendação: aguarde a adoção do Material para integrar com estilos. Use estilos nos seus próprios componentes sempre que possível.
A camada de estilo
No modelo tradicional do Compose, a personalização geralmente depende muito da substituição de tokens globais (cores e tipografia) fornecidos pelo MaterialTheme ou do encapsulamento e substituição de propriedades de um elemento combinável do sistema de design sempre que possível.
Às vezes, há propriedades na camada do Material que não são expostas pelos subsistemas ou parâmetros, mas são padrões codificados no próprio componente.
Com a API Styles, há uma nova camada de abstração que é uma ponte entre subsistemas e componentes: estilos.
| Camada | Responsabilidade | Exemplo |
|---|---|---|
| Valores do subsistema | Valores nomeados | val Primary = Color(0xFF34A85E) |
| Estilos atômicos | Estilo que faz exatamente uma mudança de propriedade | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| Estilos de componentes | Configurações específicas do componente | Um botão com plano de fundo principal e preenchimento de 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| Componentes | O elemento da interface funcional que consome um estilo. | Button(style = buttonStyle) { ... } |
Estilos atômicos x monolíticos
Com a API Styles, é possível dividir um estilo em estilos atômicos separados.
Em vez de definir estilos complexos e específicos de componentes, como baseButtonStyle, também é possível criar estilos de utilitários pequenos e de uso único. Eles atuam como seus "á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, ) ) } } }
Composição usando "then"
Um dos recursos avançados da nova API Styles é o operador then, que permite mesclar vários objetos Style. Isso permite criar um componente usando classes de utilitários atômicos.
Tradicional (não atômico):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Refatoração atômica:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
Adotar estilos no sistema de design
Considere as opções a seguir ao adotar estilos no sistema de design, dependendo de onde ele está no espectro.
Sistema de design personalizado com estilos
Considerar quando: você recebeu um guia de marca abrangente que não é baseado no Material Design e não planeja usar o Material Design.
Estratégia: implemente um sistema de design totalmente personalizado e exponha estilos como parte do tema.
Essa opção é o caminho personalizado se você não usar o Material como a linguagem principal do sistema de design. Você ignora o MaterialTheme completamente para definições visuais e
já criou seu próprio tema personalizado. Você cria um CompanyTheme que atua como um contêiner para seus estilos.
- Como funciona: crie um objeto
CompanyThemeque contenha objetosStylepara cada componente do sistema. Seus componentes (wrappers em torno da lógica do Material ou implementações personalizadas deBoxouLayout) consomem esses estilos diretamente e expõem um parâmetroStylepara consumidores do sistema de design. - A camada de estilo: os estilos são a definição principal do sistema de design. Os tokens são variáveis nomeadas alimentadas nesses estilos. Isso permite uma personalização profunda, como definir animações exclusivas para mudanças de estado (por exemplo, animação de escala e cor na pressão).
Se você estiver criando seu próprio tema personalizado sem usar o Material e quiser adotar estilos, adicione sua lista de estilos ao tema. Isso permite acessar seus estilos de base de qualquer lugar no projeto.
Crie uma classe
Stylesque armazene os vários estilos no aplicativo e crie os padrões. Por exemplo, no app Jetsnack, a classe é chamada deJetsnackStyles: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) } }
Forneça
Stylescomo parte do tema geral e exponha funções de extensão auxiliares emStyleScopepara acessar os 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, ) } }
Acesse
JetsnackStylesno elemento combinável:@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) } }
Além da adoção global de temas, há estratégias alternativas para incorporar Styles aos apps. É possível aproveitar Styles inline para sites de chamadas específicos ou usar definições estáticas quando os recursos de tema completos não forem necessários.
Styles não deve ser trocado condicionalmente, a menos que o estilo inteiro seja fundamentalmente diferente. É preferível acessar tokens dinâmicos dentro de uma definição visual em vez de alternar entre objetos de estilo distintos.