Embora o Material Design seja o sistema de design recomendado e o Jetpack Compose use uma implementação do Material Design, você tem outras opções. O Material Design foi criado com base em APIs públicas. Por isso, é possível criar seu próprio sistema de design da mesma maneira.
Há várias abordagens possíveis:
- Extensão do
MaterialTheme
com outros valores de temas - Substituição de um ou mais sistemas do Material Design (
Colors
,Typography
ouShapes
) por implementações personalizadas, mantendo os outros - Implementação de um sistema de design totalmente personalizado para
substituir o
MaterialTheme
Talvez você também queira continuar usando os componentes do Material Design em um sistema de design personalizado. É possível fazer isso, mas há alguns detalhes que precisam ser lembrados para atender à abordagem adotada.
Para saber mais sobre as construções de nível inferior e as APIs usadas no MaterialTheme
e em sistemas de design personalizados, consulte o guia Anatomia de um tema no Compose.
Como estender o tema do Material Design
O Compose modela os Temas do Material Design com cuidado para simplificar e seguir as diretrizes do Material Design com segurança de tipo. No entanto, é possível estender os conjuntos de cores, tipografias e formas com outros valores.
A abordagem mais simples é a adição de propriedades de extensão:
// Use with MaterialTheme.colorScheme.snackbarAction val ColorScheme.snackbarAction: Color @Composable get() = if (isSystemInDarkTheme()) Red300 else Red700 // Use with MaterialTheme.typography.textFieldInput val Typography.textFieldInput: TextStyle get() = TextStyle(/* ... */) // Use with MaterialTheme.shapes.card val Shapes.card: Shape get() = RoundedCornerShape(size = 20.dp)
Isso possibilita manter a consistência com as APIs de uso do MaterialTheme
. Um exemplo disso,
definido pelo próprio Compose, é o
surfaceColorAtElevation
,
que determina a cor da superfície que precisa ser usada dependendo da elevação.
Outra abordagem possível é a definição de um tema estendido que "envolva" MaterialTheme
e
os valores dele.
Você pode querer adicionar mais duas cores (caution
e onCaution
, uma
cor amarela usada para ações semiperigosas), mantendo as
cores do Material Design:
@Immutable data class ExtendedColors( val caution: Color, val onCaution: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( caution = Color.Unspecified, onCaution = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( caution = Color(0xFFFFCC02), onCaution = Color(0xFF2C2D30) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.caution object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Isso é semelhante às APIs de uso do MaterialTheme
. Essa abordagem também é compatível com vários temas,
já que você pode aninhar ExtendedTheme
s da mesma forma que MaterialTheme
.
Usar componentes do Material Design
Ao estender os Temas do Material Design, os valores MaterialTheme
existentes são mantidos
e os componentes do Material Design mantêm padrões razoáveis.
Para usar valores estendidos em componentes, envolva-os nas suas próprias funções combináveis, definindo diretamente os valores que você quer mudar e expondo outros como parâmetros:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.caution, contentColor = ExtendedTheme.colors.onCaution /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Será necessário substituir os usos de Button
por ExtendedButton
sempre que for
adequado.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Substituir subsistemas do Material Design
Em vez de estender os temas do Material Design, você pode substituir um ou mais
sistemas, Colors
, Typography
ou Shapes
, por uma implementação personalizada,
mantendo os outros.
Você pode querer, por exemplo, substituir os sistemas de tipo e forma e, ao mesmo tempo, manter o sistema de cores:
@Immutable data class ReplacementTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class ReplacementShapes( val component: Shape, val surface: Shape ) val LocalReplacementTypography = staticCompositionLocalOf { ReplacementTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalReplacementShapes = staticCompositionLocalOf { ReplacementShapes( component = RoundedCornerShape(ZeroCornerSize), surface = RoundedCornerShape(ZeroCornerSize) ) } @Composable fun ReplacementTheme( /* ... */ content: @Composable () -> Unit ) { val replacementTypography = ReplacementTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val replacementShapes = ReplacementShapes( component = RoundedCornerShape(percent = 50), surface = RoundedCornerShape(size = 40.dp) ) CompositionLocalProvider( LocalReplacementTypography provides replacementTypography, LocalReplacementShapes provides replacementShapes ) { MaterialTheme( /* colors = ... */ content = content ) } } // Use with eg. ReplacementTheme.typography.body object ReplacementTheme { val typography: ReplacementTypography @Composable get() = LocalReplacementTypography.current val shapes: ReplacementShapes @Composable get() = LocalReplacementShapes.current }
Usar componentes do Material Design
Quando um ou mais sistemas de MaterialTheme
forem substituídos, o uso de componentes
do Material Design inalterados poderá resultar em valores indesejados de cor, tipo ou forma.
Para usar valores de substituição em componentes, envolva-os nas suas próprias funções combináveis, definindo diretamente os valores do sistema relevante e expondo outros como parâmetros.
@Composable fun ReplacementButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( shape = ReplacementTheme.shapes.component, onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = ReplacementTheme.typography.body ) { content() } } ) }
Será necessário substituir os usos de Button
por ReplacementButton
sempre que for
adequado.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implementar um sistema de design totalmente personalizado
É possível substituir os Temas do Material Design por um sistema de design totalmente personalizado.
O MaterialTheme
oferece os seguintes sistemas:
Colors
,Typography
eShapes
: sistemas de Temas do Material DesignTextSelectionColors
: cores usadas para seleção de texto porText
eTextField
Ripple
eRippleTheme
: implementação deIndication
pelo Material Design
Para continuar usando os componentes do Material Design, substitua alguns desses sistemas nos seus temas personalizados ou processe-os nos seus componentes para evitar comportamentos indesejados.
No entanto, os sistemas de design não estão limitados aos conceitos de que o Material Design depende. Você pode modificar os sistemas existentes e introduzir sistemas totalmente novos, com novas classes e tipos, para tornar outros conceitos compatíveis com temas.
No código a seguir, modelamos um sistema de cores personalizado com gradientes
(List<Color>
), incluímos um sistema de tipos, apresentamos um novo sistema de elevação
e excluímos outros sistemas fornecidos pelo MaterialTheme
:
@Immutable data class CustomColors( val content: Color, val component: Color, val background: List<Color> ) @Immutable data class CustomTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class CustomElevation( val default: Dp, val pressed: Dp ) val LocalCustomColors = staticCompositionLocalOf { CustomColors( content = Color.Unspecified, component = Color.Unspecified, background = emptyList() ) } val LocalCustomTypography = staticCompositionLocalOf { CustomTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalCustomElevation = staticCompositionLocalOf { CustomElevation( default = Dp.Unspecified, pressed = Dp.Unspecified ) } @Composable fun CustomTheme( /* ... */ content: @Composable () -> Unit ) { val customColors = CustomColors( content = Color(0xFFDD0D3C), component = Color(0xFFC20029), background = listOf(Color.White, Color(0xFFF8BBD0)) ) val customTypography = CustomTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val customElevation = CustomElevation( default = 4.dp, pressed = 8.dp ) CompositionLocalProvider( LocalCustomColors provides customColors, LocalCustomTypography provides customTypography, LocalCustomElevation provides customElevation, content = content ) } // Use with eg. CustomTheme.elevation.small object CustomTheme { val colors: CustomColors @Composable get() = LocalCustomColors.current val typography: CustomTypography @Composable get() = LocalCustomTypography.current val elevation: CustomElevation @Composable get() = LocalCustomElevation.current }
Usar componentes do Material Design
Quando não houver MaterialTheme
, o uso de componentes do Material Design inalterados resultará
em comportamento de indicação e valores de cor, tipo e forma indesejados.
Para usar valores personalizados em componentes, envolva-os nas suas próprias funções de composição, definindo diretamente os valores do sistema relevante e expondo outros como parâmetros:
É recomendado acessar os valores definidos usando o tema personalizado.
Se o tema não fornecer Color
, TextStyle
, Shape
ou
outros sistemas, você também poderá fixá-los no código.
@Composable fun CustomButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = CustomTheme.colors.component, contentColor = CustomTheme.colors.content, disabledContainerColor = CustomTheme.colors.content .copy(alpha = 0.12f) .compositeOver(CustomTheme.colors.component), disabledContentColor = CustomTheme.colors.content .copy(alpha = 0.38f) ), shape = ButtonShape, elevation = ButtonDefaults.elevatedButtonElevation( defaultElevation = CustomTheme.elevation.default, pressedElevation = CustomTheme.elevation.pressed /* disabledElevation = 0.dp */ ), onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = CustomTheme.typography.body ) { content() } } ) } val ButtonShape = RoundedCornerShape(percent = 50)
Se você introduziu novos tipos de classe, como List<Color>
para representar
gradientes, pode ser melhor implementar componentes do zero em vez
de envolvê-los. Por exemplo, confira
JetsnackButton
na amostra do Jetsnack.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Material Design 3 no Compose
- Migrar do Material 2 para o Material 3 no Compose
- Anatomia de um tema no Compose