Temas no Compose

Com o Jetpack Compose, é fácil dar uma aparência consistente ao app usando temas. É possível personalizar a implementação do Material Design no Compose para se adequar à marca do seu produto. Se isso não atender às suas necessidades, crie um sistema de design personalizado usando as APIs públicas do Compose.

Temas em todo o aplicativo

O Jetpack Compose oferece uma implementação do Material Design, um sistema de design abrangente para criar interfaces digitais. Os componentes do Material Design (botões, cards, chaves e assim por diante) são baseados no Material Theming, que é uma maneira sistemática de personalizar o Material Design para refletir melhor a marca do seu produto. Um Material Theme é composto pelos atributos color, typography e shape. Quando você personaliza esses atributos, as mudanças são refletidas automaticamente nos componentes usados para criar o app.

O Jetpack Compose implementa esses conceitos com o MaterialTheme que pode ser composto:

MaterialTheme(
    colors = …,
    typography = …,
    shapes = …
) {
    // app content
}

Configure os parâmetros transmitidos a MaterialTheme para aplicar o tema no aplicativo.

Duas capturas de tela contrastantes. A primeira usa o estilo MaterialTheme padrão,
e a segunda captura usa o estilo modificado.

Figura 1. A primeira captura de tela mostra um app que não configura MaterialTheme e, portanto, usa o estilo padrão. A segunda captura de tela mostra um app que transmite parâmetros a MaterialTheme para personalizar o estilo.

Cor

As cores são modeladas no Compose com a classe Color, uma classe simples de retenção de dados.

val red = Color(0xffff0000)
val blue = Color(red = 0f, green = 0f, blue = 1f)

Embora você possa organizá-las da forma que quiser (como constantes de nível superior, em um singleton ou definidas in-line), é altamente recomendável especificar as cores no seu tema e recuperar as cores nele. Essa abordagem permite oferecer compatibilidade com vários temas, como o tema escuro.

Exemplo da paleta de cores do tema

O Compose fornece a classe Colors para modelar o sistema de cores do Material Design. Colors fornece funções builder para criar conjuntos de cores claras ou escuras:

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,
    // ...
)

Depois de definir as Colors, você poderá transmiti-las para um MaterialTheme:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

Como usar cores de tema

É possível recuperar as Colors fornecidas ao MaterialTheme que pode ser composto usando MaterialTheme.colors.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

Cor da superfície e do conteúdo

Muitos componentes aceitam um par de cores e de "cores de conteúdo":

Surface(
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    …

TopAppBar(
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    …

Isso permite não apenas definir a cor de um elemento que pode ser composto, mas também fornecer uma cor padrão para o conteúdo, os elementos que podem ser compostos contidos nele. Muitos elementos que podem ser compostos usam essa cor de conteúdo por padrão. Por exemplo, Text baseia a cor dele na cor do conteúdo do pai, e Icon usa essa cor para definir a própria tonalidade.

Dois exemplos do mesmo banner, com cores diferentes

Figura 2. Definir cores do plano de fundo diferentes produz cores de texto e ícone diferentes.

O método contentColorFor() recupera a cor "ativa" adequada para todas as cores do tema. Por exemplo, se você definir um plano de fundo primary, ele definirá onPrimary como a cor do conteúdo. Se você definir uma cor de plano de fundo que não seja do tema, também precisará especificar uma cor de conteúdo sensível. Use LocalContentColor para recuperar a cor do conteúdo atual que contrasta com o plano de fundo atual.

Conteúdo Alfa

Com frequência, queremos variar o nível de ênfase no conteúdo para comunicar a importância e apresentar uma hierarquia visual. O Material Design recomenda (link em inglês) o uso de diferentes níveis de opacidade para transmitir esses níveis de importância variados.

Isso é implementado pelo Jetpack Compose via LocalContentAlpha. É possível especificar um Alfa de conteúdo para uma hierarquia fornecendo um valor para esse CompositionLocal. Os elementos filhos que podem ser compostos podem usar esse valor. Por exemplo: Text e Icon, por padrão, usam a combinação de LocalContentColor ajustada para usar LocalContentAlpha. O Material especifica alguns valores Alfa padrão (high, medium, disabled) que são modelados pelo objeto ContentAlpha. Observe que MaterialTheme define LocalContentAlpha como ContentAlpha.high por padrão.

// 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(/*...*/)
}

Captura de tela do título de um artigo, mostrando diferentes níveis de ênfase
de texto

Figura 3. Aplique diferentes níveis de ênfase no texto para comunicar visualmente a hierarquia de informações.

Tema escuro

No Compose, você pode implementar temas claros e escuros ao fornecer conjuntos diferentes de Colors para o MaterialTheme que pode ser composto e consumir cores no tema:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

Neste exemplo, o MaterialTheme é encapsulado na própria função que pode ser composta, que aceita um parâmetro que especifica se é necessário ou não usar um tema escuro. Nesse caso, a função recebe o valor padrão para darkTheme consultando a configuração do tema do dispositivo.

Ao implementar um tema escuro, é possível verificar se as Colors atuais são claras ou escuras:

val isLightTheme = MaterialTheme.colors.isLight

Esse valor é definido pelas funções builder lightColors() e darkColors().

No Material Design, as superfícies em temas escuros com elevações mais altas recebem sobreposições de elevação, que clareiam o plano de fundo. Essas sobreposições são implementadas automaticamente pela Surface que pode ser composta ao usar cores escuras:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

Captura de tela de um app, mostrando as cores levemente diferentes usadas para elementos
em diferentes níveis de elevação

Figura 4. Os cards e a navegação inferior são coloridas com surface como o plano de fundo, mas, como estão em uma elevação maior, a cor delas é um pouco mais clara.

Como estender cores do Material Design

O Compose modela os temas de cores do Material Design com cuidado para simplificar e seguir as diretrizes do Material Design com segurança de tipo. Se precisar estender o conjunto de cores, você poderá implementar seu próprio sistema de cores como mostrado abaixo ou adicionar extensões:

@Composable
val Colors.snackbarAction: Color
    get() = if (isLight) Red300 else Red700

Tipografia

O Material Design define um sistema de tipos, incentivando você a usar um pequeno número de estilos com nomes semânticos.

Exemplo de várias fontes diferentes em vários
estilos

O Compose implementa o sistema de tipos com Typography, TextStyle e classes relacionadas a fontes. O construtor Typography oferece padrões a cada estilo para que você possa omitir qualquer um que não quiser personalizar:

val Rubik = FontFamily(
    Font(R.font.rubik_regular),
    Font(R.font.rubik_medium, FontWeight.W500),
    Font(R.font.rubik_bold, FontWeight.Bold)
)

val MyTypography = Typography(
    h1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = MyTypography, /*...*/)

Se você quiser usar a mesma fonte, especifique o parâmetro defaultFontFamily e omita a fontFamily dos elementos TextStyle:

val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)

Como usar estilos de texto

Recupere o TextStyle do tema, como mostrado neste exemplo:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

Captura de tela mostrando uma mistura de fontes diferentes para finalidades distintas

Figura 5. Use uma seleção de fontes e estilos para representar sua marca.

Forma

O Material Design define um sistema de formas, permitindo que você defina formas para componentes grandes, médios e pequenos.

Mostra uma variedade de formas do Material Design

O Compose implementa o sistema de formas com a classe Shapes, que permite especificar uma CornerBasedShape para cada categoria:

val Shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomStart = 16.dp,
        bottomEnd = 0.dp
    )
)

MaterialTheme(shapes = Shapes, /*...*/)

Muitos componentes usam essas formas por padrão. Por exemplo: Button, TextField e FloatingActionButton têm o valor padrão pequeno, AlertDialog segue o padrão médio, e ModalDrawerLayout o padrão grande. Consulte a referência do esquema de formas para ver o mapeamento completo.

Como usar formas

Recupere as formas do tema:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

Captura de tela de um app que usa formas do Material Design para transmitir o estado em que um elemento
se encontra

Figura 6. Use formas para expressar a marca ou o estado.

Estilos de componente

Não há um conceito explícito de estilos de componente no Compose, já que você oferece essa funcionalidade ao criar seus próprios elementos que podem ser compostos. Por exemplo, para criar um estilo de botão, encapsule um botão em sua própria função que pode ser composta, definindo diretamente os parâmetros que você quer mudar e expondo outros como parâmetros à composição que contém os parâmetros.

@Composable
fun LoginButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonConstants.defaultButtonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Sistemas de design personalizados

Embora o Material Design seja o sistema de design recomendado por nós e o Jetpack Compose use uma implementação do Material Design, você não está restrito a usar apenas ele. É possível criar seu próprio sistema de design da mesma forma. O Material Design foi criado usando somente APIs públicas que podem ser aplicadas a outros sistemas.

Uma descrição completa de como criar um sistema de design personalizado está além do escopo deste documento, mas consulte os seguintes recursos:

Saiba mais

Para saber mais, veja o codelab de temas do Jetpack Compose.