Temas do Material Design no Compose

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 nos Temas do Material Design, uma maneira sistemática de personalizar o Material Design para refletir melhor a marca do seu produto. Um Tema do Material Design é 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 elemento 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 o suporte fácil ao tema escuro e a temas aninhados.

Exemplo da paleta de cores do tema

Figura 2. O sistema de cores do Material Design.

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 3. Definir cores diferentes para o plano de fundo 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 uma cor primary para o plano de fundo em Surface, ela usará essa função para definir onPrimary como a cor do conteúdo. Se você definir uma cor que não seja do tema para o plano de fundo, precisará especificar também uma cor de conteúdo adequada. Use LocalContentColor para recuperar a cor de conteúdo preferencial para o plano de fundo atual em uma determinada posição na hierarquia.

Conteúdo Alfa

Muitas vezes, você quer variar o nível de ênfase no conteúdo para comunicar a importância e apresentar uma hierarquia visual. As recomendações de legibilidade do texto do Material Design aconselham a implementação de diferentes níveis de opacidade para transmitir níveis de importância distintos.

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 que podem ser compostos aninhados usam esse valor para aplicar o tratamento Alfa ao conteúdo. Por exemplo, Text e Icon, por padrão, usam a combinação de LocalContentColor ajustada para usar LocalContentAlpha. O Material Design especifica alguns valores Alfa padrão (high, medium, disabled), que são modelados pelo objeto ContentAlpha.

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

Para saber mais sobre o CompositionLocal, confira o Guia de dados com escopo local no CompositionLocal.

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

Figura 4. Aplique diferentes níveis de ênfase no texto para comunicar visualmente a hierarquia de informações. A primeira linha do texto é o título e tem as informações mais importantes. Portanto, ela usa ContentAlpha.high. A segunda linha contém metadados menos importantes e, portanto, usa ContentAlpha.medium.

Tema escuro

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

@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.

Você pode usar um código como este para conferir se o Colors atual é claro ou escuro:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
          R.drawable.ic_sun_24dp
        } else {
          R.drawable.ic_moon_24dp
        }
    ),
    contentDescription = "Theme"
)

Sobreposições de elevação

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. Quanto maior a elevação de uma superfície (elevando-a mais perto de uma fonte de luz implícita), mais clara ela se tornará.

Essas sobreposições são aplicadas automaticamente pela Surface que pode ser composta ao usar cores escuras e por qualquer outro elemento que pode ser composto do Material Design ao usar uma superfície:

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 5. Os cards e a navegação inferior estão usando a cor surface como plano de fundo. Como os cards e a navegação inferior estão em níveis de elevação diferentes acima do plano de fundo, eles têm cores um pouco distintas: os cards são mais claros que o plano de fundo, e a navegação inferior é mais clara que os cards.

Para cenários personalizados, que não envolvem uma Surface, use LocalElevationOverlay, um CompositionLocal que contém a ElevationOverlay usada por componentes Surface:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

Para desativar as sobreposições de elevação, forneça null no ponto desejado em uma hierarquia que pode ser composta:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

Tons de cores limitados

O Material Design recomenda aplicar tons de cores limitados para temas escuros, dando preferência à cor surface em vez da cor primary na maioria dos casos. Os elementos que podem ser compostos do Material Design, como TopAppBar e BottomNavigation, implementam esse comportamento por padrão.

Figura 6. Tema escuro do Material Design com tons de cor limitados. A barra de apps superior usa a cor principal no tema claro e a cor da superfície no tema escuro.

Para cenários personalizados, use a propriedade de extensão primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

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

Figura 7. O sistema de tipos do Material Design.

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 queira 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 defaultFontFamily parameter e omita a fontFamily dos elementos TextStyle:

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

Como usar estilos de texto

TextStyles são acessados por MaterialTheme.typography. Recupere os TextStyles desta maneira:

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

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

Figura 8. 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

Figura 9. O sistema 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,
        bottomEnd = 0.dp,
        bottomStart = 16.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 ModalDrawer, o padrão grande. Consulte a referência do esquema de formas para ver o mapeamento completo.

Como usar formas

Shapes são acessados por MaterialTheme.shapes. Recupere os Shapes com um código como este:

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 10. Use formas para expressar a marca ou o estado.

Estilos padrão

Não há um conceito equivalente no Compose de estilos padrão das visualizações do Android. Você pode fornecer funcionalidades semelhantes criando suas próprias funções que podem ser compostas "sobrecarregadas" para envolver os componentes do Material Design. Por exemplo, para criar um estilo de botão, envolva um botão na 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 os contêm.

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

Sobreposições de tema

Você pode conseguir o equivalente às sobreposições de tema das visualizações do Android no Compose aninhando elementos MaterialTheme que podem ser compostos. Como o MaterialTheme define as cores, a tipografia e as formas como o valor do tema atual, se um tema definir apenas um desses parâmetros, os outros manterão os valores padrão.

Além disso, ao migrar telas baseadas em visualização para o Compose, preste atenção aos usos do atributo android:theme. É provável que você precise de um novo MaterialTheme nessa parte da árvore de IU do Compose.

Na amostra do Owl (link em inglês), a tela de detalhes usa um PinkTheme para a maior parte da exibição e um BlueTheme para a seção relacionada. Veja a captura de tela e o código abaixo.

Figura 11. Temas aninhados na amostra do Owl.

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

Estados dos componentes

Os componentes do Material Design com que você pode interagir (clicar, alternar etc.) podem estar em diferentes estados visuais. Alguns estados: ativado, desativado, pressionado etc.

Os elementos que podem ser compostos geralmente têm um parâmetro enabled. Defini-lo como false impede a interação e muda propriedades como cor e elevação para expressar visualmente o estado do componente.

Figura 12. Botão com enabled = true (à esquerda) e enabled = false (à direita).

Na maioria dos casos, você pode confiar nos valores padrão de cor e elevação. Se você quiser configurar valores usados em estados diferentes, há classes e funções de conveniência disponíveis. Veja este exemplo:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

Figura 13. Botão com enabled = true (à esquerda) e enabled = false (à direita), com valores de cor e elevação ajustados.

Ondulações

Os componentes do Material Design usam ondulações para indicar interação. Se você estiver usando MaterialTheme na hierarquia, uma Ripple será usada como a Indication padrão dentro de modificadores, como clickable e indication.

Na maioria dos casos, você pode confiar na Ripple padrão. Caso queira configurar a aparência das ondulações, use RippleTheme para mudar propriedades como cor e Alfa.

É possível estender RippleTheme e usar as funções utilitárias defaultRippleColor e defaultRippleAlpha. Em seguida, você pode fornecer seu tema de ondulação personalizado na hierarquia usando LocalRippleTheme:

@Composable
fun MyApp() {
  MaterialTheme {
    CompositionLocalProvider(
      LocalRippleTheme provides SecondaryRippleTheme
    ) {
      // App content
    }
  }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
  @Composable
  override fun defaultColor() = RippleTheme.defaultRippleColor(
    contentColor = MaterialTheme.colors.secondary,
    lightTheme = MaterialTheme.colors.isLight
  )

  @Composable
  override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
    contentColor = MaterialTheme.colors.secondary,
    lightTheme = MaterialTheme.colors.isLight
  )
}

alt_text

Figura 14. Botões com diferentes valores de ondulação fornecidos pelo RippleTheme.

Material Design 3 e Material You

O Jetpack Compose oferece uma implementação do Material Design 3, a próxima evolução do Material Design. O Material 3 inclui temas e componentes atualizados, além de recursos de personalização do Material You, como cores dinâmicas, e foi desenvolvido para ser coeso com o novo estilo visual do Android 12 e com a IU do sistema.

Para começar, adicione a nova dependência do Compose Material 3 aos arquivos build.gradle:

implementation "androidx.compose.material3:material3:material3_version"

Um tema do M3 contém valores de esquema de cores e tipografia, com atualizações nas formas em breve. Quando você personaliza esses valores, as mudanças são refletidas automaticamente nos componentes do M3 usados para criar o app.

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

MaterialTheme(
    colorScheme = …,
    typography = …
    // Updates to shapes coming soon
) {
    // M3 app content
}

Configure os parâmetros transmitidos ao MaterialTheme do M3 para aplicar o tema ao aplicativo.

Figura 15. As duas primeiras capturas de tela mostram um app que não configura o MaterialTheme do M3. Portanto, ele usa o estilo padrão. As outras duas capturas de tela mostram um app que transmite parâmetros ao MaterialTheme para personalizar o estilo.

Esquema de cores

O Material Design 3 separa as cores em "slots" nomeados, como "primary", "background" e "error", que são usados pelos componentes do Material 3. Juntos, esses slots formam um esquema de cores. Os valores de cor usados por cada slot são desenhados a partir de um conjunto de paletas tonais, selecionadas para satisfazer os requisitos de acessibilidade (por exemplo, o slot "primary" tem contraste garantido com o slot "on primary"). Os esquemas de cores oferecem novas cores padrão de referência para temas claros e escuros.

Figura 16. Paletas tonais e esquemas de cores do Material 3, com valores de referência de cor.

O Compose fornece a classe ColorScheme para modelar o esquema de cores do Material 3. O ColorScheme fornece funções do builder para criar um esquema de cores claro ou escuro:

private val Blue40 = Color(0xff1e40ff​​)
private val DarkBlue40 = Color(0xff3e41f4)
private val Yellow40 = Color(0xff7d5700)
// Remaining colors from tonal palettes

private val LightColorScheme = lightColorScheme(
    primary = Blue40,
    secondary = DarkBlue40,
    tertiary = Yellow40,
    // error, primaryContainer, onSecondary, etc.
)
private val DarkColorScheme = darkColorScheme(
    primary = Blue80,
    secondary = DarkBlue80,
    tertiary = Yellow80,
    // error, primaryContainer, onSecondary, etc.
)

Depois de definir o ColorScheme, você poderá transmiti-lo para um MaterialTheme do M3:

val darkTheme = isSystemInDarkTheme()
MaterialTheme(
    colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
) {
    // M3 app content
}

Como gerar esquemas de cores

Embora você possa criar um ColorScheme personalizado manualmente, geralmente é mais fácil gerar um usando as cores de origem da sua marca. A ferramenta Material Theme Builder possibilita fazer isso e, como opção, exportar o código de temas do Compose.

Figura 17. Paletas tonais e esquemas de cores do Material 3 com valores de cores personalizados, gerados pela ferramenta Material Theme Builder.

Esquemas de cores dinâmicas

Cor dinâmica é a parte principal do Material Design, em que um algoritmo deriva cores personalizadas do plano de fundo de um usuário para ser aplicado aos apps e à IU do sistema dele. Essa paleta de cores é usada como ponto de partida para gerar um esquema completo de cores claras e escuras.

As cores dinâmicas estão disponíveis no Android 12 e em versões mais recentes. Se a cor dinâmica estiver disponível, será possível configurar um ColorScheme dinâmico. Caso contrário, você pode voltar a usar um ColorScheme personalizado claro ou escuro.

O ColorScheme fornece funções do builder para criar um esquema de cores dinâmicas claro ou escuro:

// Dynamic color is available on Android 12+
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
    dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
    dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}

Figura 18. As duas primeiras capturas de tela mostram um app que usa um ColorScheme dinâmico com base em um plano de fundo vermelho. As outras duas capturas de tela mostram um app que usa um ColorScheme dinâmico com base em um plano de fundo azul.

Como usar cores no esquema de cores

É possível recuperar o ColorScheme fornecido ao MaterialTheme do M3 que pode ser composto usando MaterialTheme.colorScheme:

Text(
    text = "Hello M3 theming",
    color = MaterialTheme.colorScheme.tertiary
)

Tipografia

O Material Design 3 define uma escala de tipo, incluindo estilos de texto que foram adaptados do Material Design 2. A nomenclatura e o agrupamento foram simplificados para: display, headline, title, body e label, com tamanhos grandes, médios e pequenos para cada um.

Figura 19. Escala de tipo do Material 3 x escala de tipo do Material 2.

O Compose oferece a classe Typography do M3, junto com a classe TextStyle existente e classes relacionadas a fontes para modelar a escala de tipo do Material 3:

val KarlaFontFamily = FontFamily(
    Font(R.font.karla_regular),
    Font(R.font.karla_bold, FontWeight.Bold)
)

val AppTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = KarlaFontFamily,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    // titleMedium, labelSmall, etc.
)

Depois de definir a Typography, você poderá transmiti-la para um MaterialTheme do M3:

MaterialTheme(
    typography = AppTypography
) {
    // M3 app content
}

Como usar estilos de texto

É possível recuperar a Typography fornecida ao elemento composto MaterialTheme do M3 usando MaterialTheme.typography:

Text(
    text = "Hello M3 theming",
    style = MaterialTheme.typography.bodyLarge
)

Elevação

O Material 3 representa a elevação usando principalmente sobreposições de cores tonais. Essa é uma nova maneira de distinguir contêineres e superfícies, aumentando a elevação tonal usando um tom mais proeminente, além de sombras.

As sobreposições de elevação no tema escuro também mudaram para sobreposições de cores tonais no Material 3.

A cor de sobreposição vem do slot de cores principal.

Figura 20. Elevação do Material 3 x elevação do Material 2, no tema claro e escuro.

A Surface do M3, o elemento que pode ser composto por trás da maioria dos componentes do M3, inclui suporte para a elevação de tonalidade e sombra:

Surface(
    tonalElevation = 16.dp,
    shadowElevation = 16.dp
) {
    // Surface content
}

IU do sistema

Alguns aspectos do Material You vêm do novo estilo visual do Android 12 e da IU do sistema. As duas áreas principais em que há mudanças são no Ripple e no Overscroll. Nenhum trabalho adicional é necessário para implementar essas alterações.

Ripple

O Ripple agora usa um brilho sutil para iluminar superfícies quando pressionado. O Compose Material Ripple usa um RippleDrawable da plataforma disponível internamente no Android. Portanto, a ondulação brilhante está disponível no Android 12 ou posterior para todos os componentes do Material.

Figura 21. Ripple do Android 12 versus Ripple nas versões do Android anteriores à 12.

Overscroll

O Overscroll agora usa um efeito de alongamento na borda dos contêineres de rolagem. O alongamento de rolagem é ativado por padrão na rolagem dos elementos que podem ser compostos do contêiner (por exemplo, LazyColumn, LazyRow e LazyVerticalGrid) no Compose Foundation 1.1.0 ou posterior, independentemente do nível da API.

Figura 22. Alongamento da rolagem.

Saiba mais

Para saber mais sobre os temas do Material Design no Compose, consulte os recursos a seguir.

Codelabs

Vídeos