Aplicação de temas no Compose com o Material 3

1. Introdução

Neste codelab, você vai aprender sobre a aplicação de temas no Jetpack Compose usando o Material Design 3. Você também vai aprender sobre os principais elementos básicos dos esquemas de cores, da tipografia e das formas do Material Design 3, que ajudam a aplicar temas no seu app de maneira personalizada e acessível.

Além disso, você vai conhecer a compatibilidade com temas dinâmicos e diferentes níveis de ênfase.

O que você vai aprender

Neste codelab, você vai aprender o seguinte:

  • Principais aspectos da aplicação de temas do Material 3
  • Esquemas de cores do Material 3 e como gerar temas para seu app
  • Como oferecer suporte à aplicação de temas dinâmicos e claros/escuros para seu app
  • Tipografia e formas para personalizar seu app
  • Componentes do Material 3 e personalização para estilizar seu app

O que você vai criar

Neste codelab, você vai criar um tema para um app de e-mail cliente chamado Reply. Vamos começar com um aplicativo sem estilo usando o tema de referência e usar o que você aprendeu para aplicar um tema no aplicativo e oferecer suporte a temas escuros.

d15db3dc75a9d00f.png

Ponto de partida padrão do app com o tema de referência.

Você vai criar seu tema com esquema de cores, tipografia e formas e, depois, vai aplicá-lo à lista de e-mails e à página de detalhes do app. Você também vai oferecer suporte a temas dinâmicos no app. Ao final do codelab, seu app vai permitir o uso de temas dinâmicos e de cores.

Material 3 claro

Ponto final do codelab de aplicação de temas de cores claras e temas dinâmicos claros.

Material 3 escuro

Ponto final do codelab de aplicação de temas de cores escuras e temas dinâmicos escuros.

O que é necessário

2. Etapas da configuração

Nesta etapa, você vai fazer o download do código completo do app Reply que será estilizado neste codelab.

Acessar o código

O código deste codelab pode ser encontrado no repositório codelab-android-compose do GitHub. Para cloná-lo, execute:

$ git clone https://github.com/android/codelab-android-compose

Outra opção é fazer o download de dois arquivos ZIP:

Conferir o app de exemplo

O código que você acabou de baixar contém todos os codelabs disponíveis do Compose. Para concluir este codelab, abra o projeto ThemingCodelab no Android Studio.

Recomendamos que você comece com o código na ramificação principal (main) e siga todas as etapas do codelab no seu ritmo. Você pode executar qualquer uma das versões no Android Studio a qualquer momento, modificando a ramificação do git do projeto.

Conhecer o código inicial

O código principal contém um pacote de interface, que tem os seguintes pacotes e arquivos principais com os quais você vai interagir:

  • MainActivity.kt: atividade do ponto de entrada em que você inicia o app Reply.
  • com.example.reply.ui.theme: este pacote contém temas, tipografia e esquemas de cores. Você vai adicionar temas do Material Design a este pacote.
  • com.example.reply.ui.components: contém os componentes personalizados do app, como "Itens de lista", "Barras de apps" etc. Você vai aplicar temas a esses componentes.
  • ReplyApp.kt: é a principal função combinável em que a árvore da interface vai começar. Você vai aplicar temas de nível superior neste arquivo.

Este codelab se concentra em arquivos de pacote ui.

3. Aplicação de temas com o Material 3

O Jetpack Compose oferece uma implementação do Material Design, um sistema de design abrangente para criar interfaces digitais. Os componentes desse sistema (botões, cards, chaves e assim por diante) são criados com base em Temas do Material Design. Assim, eles oferecem uma maneira sistemática de personalizar o Material Design para refletir melhor a marca do seu produto.

O tema do Material 3 é composto pelos seguintes subsistemas para adicionar temas ao app: esquema de cores, tipografia e formas. Quando você personaliza esses valores, as mudanças são refletidas automaticamente nos componentes do M3 usados para criar o app. Vamos nos aprofundar em cada subsistema e implementá-los no app de exemplo.

Subsistemas do Material Design: cores, tipografia e formas.

Subsistema de cores, tipografia e formas do Material 3.

4. Esquemas de cores

A base de um esquema de cores é o conjunto de cinco cores principais, cada uma relacionada a uma paleta tonal de 13 tons, usada pelos componentes do Material 3.

Cinco cores principais de valor de referência para criar um tema do M3.

Cinco cores principais de valor de referência para criar um tema do M3.

Cada cor de destaque (primária, secundária e terciária) é fornecida em quatro cores compatíveis de tons diferentes para pareamento, definição de ênfase e expressão visual.

Quatro cores tonais das cores de destaque primária, secundária e terciária.

Quatro cores tonais das cores de destaque primária, secundária e terciária.

Da mesma forma, as cores neutras também são divididas em quatro tons compatíveis usados para superfícies e plano de fundo. Eles também são importantes para destacar ícones de texto quando colocados em qualquer superfície.

Quatro cores tonais neutras de referência.

Quatro cores tonais neutras de referência.

Leia mais sobre o Esquema de cores e funções de cor.

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.

Você pode escolher a cor que quiser, mas, para nosso caso de uso, vai usar a cor primária padrão do app Reply, a #825500. Clique em Primárias na seção Cores primárias à esquerda e adicione o código no seletor de cores.

294f73fc9d2a570e.png

Adicionando o código de cor primária no Material Theme Builder.

Depois que você adiciona a cor primária no Material Theme Builder, o tema a seguir e a opção de exportação aparecem no canto superior direito. Neste codelab, você vai exportar o tema no Jetpack Compose.

Material Theme Builder com opção de exportação no canto superior direito.

Criador de Temas do Material Design com a opção de exportação no canto superior direito.

A cor primária #825500 gera o tema a seguir, que você vai adicionar ao app. O Material 3 oferece uma ampla variedade de funções de cor para expressar com flexibilidade o estado, o destaque e a ênfase de um componente.

Esquema de cores claras e escuras exportado da cor primária.

Esquema de cores claras e escuras exportado da cor primária.

O arquivo The Color.kt gerado contém as cores do seu tema com todos as funções definidos para cores de temas claro e escuro.

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)

val seed = Color(0xFF825500)

O arquivo The Theme.kt gerado contém uma configuração para esquemas de cores claras e escuras e o tema do app. Ele também contém a principal função combinável de aplicação de temas, AppTheme().

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable

private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)

private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

O principal elemento para implementar temas no Jetpack Compose é o elemento combinável MaterialTheme.

Una o elemento combinável MaterialTheme() à função AppTheme(), que usa dois parâmetros:

  • useDarkTheme: esse parâmetro está vinculado à função isSystemInDarkTheme() para observar as configurações do sistema relativas à aplicação de temas e aplicar o tema claro ou escuro. Para manter o app em um tema claro ou escuro manualmente, transmita um valor booleano para o useDarkTheme.
  • content: o conteúdo ao qual o tema será aplicado.

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Se você tentar executar o app agora, vai notar que ele tem a mesma aparência. Mesmo que você tenha importado nosso novo esquema de cores com novas cores para a aplicação de tema, os temas de linha de base ainda vão aparecer, porque você não aplicou o tema ao app Compose.

App com o tema de referência quando nenhum tema é aplicado.

App com o tema de referência quando nenhum tema é aplicado.

Para aplicar o novo tema, em MainActivity.kt, envolva o ReplyApp combinável principal com a função de tema principal, AppTheme().

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

Você também vai atualizar as funções de visualização para conferir o tema aplicado às visualizações do app. Una o elemento combinável ReplyApp a ReplyAppPreview() com o AppTheme para aplicar temas às visualizações.

Como você tem os temas claro e escuro do sistema definidos nos parâmetros de visualização, os dois serão mostrados.

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

Se você executar o app agora, as cores do tema importado vão aparecer no app, em vez do tema de referência.

fddf7b9cc99b1fe3.png be7a661b4553167b.png

App com tema de referência (à esquerda).

App com tema de cor importado (à direita).

674cec6cc12db6a0.png

Visualizações de apps claros e escuros com temas de cores importados.

O Material 3 oferece suporte para esquemas de cores claras e escuras. Você uniu o app ao tema importado. Os componentes do Material 3 estão usando funções de cor padrão.

Vamos aprender sobre as funções de cor e usá-las antes de serem adicionadas ao app.

Funções de cor e acessibilidade

Cada função de cor pode ser usada em vários lugares, dependendo do estado, do destaque e da ênfase do componente.

1f184a05ea57aa84.png

Cores de funções primárias, secundárias e terciárias.

Primária é a cor de base, que é usada para os principais componentes, como botões em destaque e estados ativos.

A cor secundária principal é usada para componentes menos proeminentes na interface, por exemplo, ícones de filtro.

Já a cor terciária principal é usada para fornecer tons contrastantes, e cores neutras são usadas para o plano de fundo e as superfícies do app.

O sistema de cores do Material Design oferece valores de tom padrão e medições que podem ser usadas para atender às proporções de contraste acessíveis. Use cores não primárias sobre a primária, contêineres não primários sobre os primários e o mesmo para outros destaques e cores neutras. Assim, é possível fornecer um contraste acessível ao usuário.

Para mais informações, consulte Funções de cor e acessibilidade.

Elevação de tonalidade e sombra

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 com um tom mais proeminente, além de sombras.

Elevação de tonalidade com elevação de sombra Elevação tonal no nível 2, que usa cores do slot de cores primárias.

As sobreposições de elevação em temas escuros também mudaram para sobreposições de cores tonais no Material Design 3. A cor de sobreposição vem do slot de cores principal.

A Superfície do M3, o elemento combinável por trás da maioria dos componentes do M3, inclui suporte para a elevação de tonalidade e sombra:

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

Como adicionar cores ao app

Se você executar o app, as cores exportadas serão mostradas nos locais em que os componentes estão usando cores padrão. Agora que sabemos sobre funções e uso de cores, vamos definir um tema para o app com as funções de cor corretas.

be7a661b4553167b.png

App com tema de cores e componentes usando as funções de cor padrão.

Cores da superfície

Na tela inicial, comece encapsulando o elemento combinável do app principal em uma Surface() para fornecer a base à inclusão do conteúdo. Abra MainActivity.kt e envolva o elemento combinável ReplyApp() com Surface.

Você também vai fornecer uma elevação de 5 dp para dar à superfície uma cor tonal de espaço principal. Isso ajuda a fornecer contraste em relação ao item da lista e à barra de pesquisa na parte de cima. Por padrão, a elevação de tonalidade e sombra para a superfície é 0 dp.

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

Se você executar o aplicativo agora e verificar a lista e a página de detalhes, vai notar que a superfície tonal está aplicada em todo o app.

be7a661b4553167b.png e70d762495173610.png

Plano de fundo do app sem cor de superfície nem cor tonal (à esquerda).

Plano de fundo do app com cor de superfície e cor tonal aplicadas (à direita).

Cores da barra de apps

Nossa barra de pesquisa personalizada na parte de cima não tem um plano de fundo claro, como solicitado pelo design. Por padrão, ela retorna à superfície base padrão. Você pode fornecer um plano de fundo para oferecer uma separação clara.

5779fc399d8a8187.png

Barra de pesquisa personalizada sem plano de fundo (à esquerda).

Barra de pesquisa personalizada com plano de fundo (à direita).

Agora você vai editar ui/components/ReplyAppBars.kt, que contém a barra de apps. Você vai adicionar MaterialTheme.colorScheme.background ao Modifier do elemento combinável Row.

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

Agora aparece uma separação clara entre a superfície tonal e a barra de apps com a cor do plano de fundo.

b1b374b801dadc06.png

Barra de pesquisa com cor de fundo sobre a superfície tonal.

Cores do botão de ação flutuante

70ceac87233fe466.png

Botão de ação flutuante grande sem aplicação de tema (à esquerda).

Botão de ação flutuante grande com aplicação de tema e cores terciárias (direita).

Na tela inicial, você pode melhorar a aparência do botão de ação flutuante (FAB, na sigla em inglês) para que ele se destaque como um botão call-to-action. Para implementar isso, é necessário aplicar uma cor de destaque terciária à tela.

No arquivo ReplyListContent.kt, atualize a containerColor da cor do FAB para tertiaryContainer e a cor do conteúdo para onTertiaryContainer a fim de manter a acessibilidade e o contraste de cores.

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/
  }
}

Execute o app para conferir o FAB com tema aplicado. Neste codelab, você vai usar um LargeFloatingActionButton.

Cores de cards

A lista de e-mails na tela inicial usa um componente card. Por padrão, é um card preenchido que usa a cor da variante de superfície na cor do contêiner para fornecer uma separação clara entre a superfície e a cor do card. O Compose também oferece implementações de ElevatedCard e OutlinedCard.

É possível destacar alguns itens importantes, fornecendo tons de cores secundários. Para modificar ui/components/ReplyEmailListItem.kt, atualize a cor do contêiner do card usando CardDefaults.cardColors() para e-mails importantes:

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/
}

5818200be0b01583.png 9367d40023db371d.png

Destaque o item da lista usando a cor secundária do contêiner na superfície tonal.

Cor dos itens da lista de detalhes

Agora, você definiu um tema para sua tela inicial. Clique em qualquer um dos itens da lista de e-mails para conferir a página de detalhes.

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

Página de detalhes padrão sem aplicação de tema nos itens de lista (à esquerda).

Item da lista de detalhes com aplicação de tema de plano de fundo (à direita).

Como nenhuma cor é aplicada ao item da lista, ele volta à cor de superfície tonal padrão. Você vai aplicar uma cor de plano de fundo ao item da lista para criar uma separação e adicionar padding para criar uma espaçamento ao redor do plano de fundo.

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

Ao fornecer o plano de fundo, você tem uma separação clara entre a superfície tonal e os itens da lista.

Agora, as páginas iniciais e de detalhes têm funções e uso de cores corretos. Vamos conferir como seu app pode usar cores dinâmicas para fornecer uma experiência ainda mais personalizada e coesa.

5. Como adicionar cores dinâmicas ao app

Cor dinâmica é a parte principal do Material 3, em que um algoritmo deriva cores personalizadas do plano de fundo de um usuário para ser aplicado aos apps e à interface do sistema dele.

A aplicação de temas dinâmicos torna seus apps mais personalizados. Ela também oferece aos usuários uma experiência coesa e integrada com o tema do sistema.

As cores dinâmicas estão disponíveis no Android 12 e em versões mais recentes. Se uma cor dinâmica está disponível, você pode configurar um esquema de cores dinâmicas usando dynamicDarkColorScheme() ou dynamicLightColorScheme(). Caso contrário, volte a usar um ColorScheme claro ou escuro padrão.

Substitua o código da função AppTheme no arquivo Theme.kt pelo código abaixo:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

Tema dinâmico retirado do plano de fundo do Android 13.

Ao executar o app agora, a aplicação de temas dinâmicos aparece usando o plano de fundo padrão do Android 13.

A barra de status também pode ser estilizada dinamicamente, dependendo do esquema de cores usado para definir o tema do app.

1095e2b2c1ffdc14.png

App sem aplicação de cores na barra de status (esquerda).

App com aplicação de cores na barra de status (à direita).

Para atualizar a cor da barra de status de acordo com a cor primária do tema, adicione a cor da barra de status após a seleção do esquema de cores no elemento combinável AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

Ao executar o app, a barra de status aparece com o tema de cores primárias. Você também pode testar temas dinâmicos claros e escuros mudando o tema escuro do sistema.

69093b5bce31fd43.png

Tema dinâmico claro (esquerda) e escuro (direita) aplicado ao plano de fundo padrão do Android 13.

Até agora, você aplicou cores ao app, o que melhorou a aparência dele. No entanto, podemos notar que todo o texto no app é do mesmo tamanho. Então, agora, você pode adicionar tipografia ao app.

6. Tipografia

O Material Design 3 define uma escala de tipos. A nomenclatura e o agrupamento foram simplificados para: display, headline, title, body e label, com tamanhos grande, médio e pequeno para cada um.

999a161dcd9b0ec4.png

Escala de tipos do Material 3.

Como definir a tipografia

O Compose oferece a classe Typography do M3 e as classes TextStyle e font-related já existentes para modelar a escala de tipos do Material 3.

O construtor de tipografia oferece padrões a cada estilo para que você possa omitir os parâmetros que não quiser personalizar. Para mais informações, consulte os estilos e valores padrão de tipografia

Você vai usar cinco estilos de tipografia no seu app: headlineSmall, titleLarge, bodyLarge, bodyMedium e labelMedium. Esses estilos se aplicam à tela inicial e à tela de detalhes.

Tela mostrando o uso de tipografia do título, rótulo e estilo do corpo.

Tela mostrando o uso de tipografia do título, rótulo e estilo do corpo.

Em seguida, acesse o pacote ui/theme e abra Type.kt. Adicione o seguinte código para fornecer sua própria implementação de alguns estilos de texto em vez de valores padrão:

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

Sua tipografia foi definida. Para adicioná-la ao tema, transmita-o ao elemento combinável MaterialTheme() dentro do AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       content = content
   )
}

Como trabalhar com tipografia

Assim como nas cores, você acessa o estilo de tipografia do tema atual usando MaterialTheme.typography. Dessa forma, você tem a instância de tipografia para usar toda a tipografia definida em Type.kt.

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

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

Seu produto provavelmente não vai precisar de todos os 15 estilos padrão da escala de tipografia do Material Design. Neste codelab, são escolhidos cinco tamanhos e o restante é omitido.

Como você não aplicou tipografia aos elementos combináveis Text(), todo o texto volta para Typography.bodyLarge por padrão.

Tipografia da lista na tela inicial

Em seguida, aplique a tipografia à função ReplyEmailListItem em ui/components/ReplyEmailListItem.kt para diferenciar títulos de rótulos:

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

Tela inicial sem aplicação de tipografia (à esquerda).

Tela inicial com aplicação de tipografia (à direita).

Tipografia da lista de detalhes

Da mesma forma, você vai adicionar a tipografia na tela de detalhes atualizando todos os elementos combináveis de texto de ReplyEmailThreadItem em ui/components/ReplyEmailThreadItem.kt:

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

Tela de detalhes sem aplicação de tipografia (à esquerda).

Tela de detalhes com aplicação de tipografia (à direita).

Como personalizar a tipografia

Com o Compose, é muito fácil personalizar seu estilo de texto ou fornecer uma fonte personalizada. Você pode modificar o TextStyle para personalizar o tipo de fonte, a família de fontes, o espaçamento entre letras etc.

Você pode mudar o estilo de texto no arquivo theme/Type.kt, que será refletido em todos os componentes que o utilizam.

Atualize fontWeight para SemiBold e lineHeight para 32.sp em titleLarge, que é usado para o assunto no item da lista. Isso dá mais ênfase ao assunto e deixa clara a separação.

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

Como aplicar tipografia personalizada ao texto do assunto.

7. Formas

As superfícies do Material Design podem ser mostradas em diferentes formas. As formas podem ser usadas para chamar atenção, identificar os componentes, comunicar o estado e expressar a marca.

Como definir formas

O Compose oferece à classe Shapes parâmetros expandidos para implementar novas formas do M3. A escala de formas do M3, assim como a escala de tipos, permite um intervalo expressivo de formas em toda a interface.

Há diferentes tamanhos na escala de formas:

  • Muito pequena
  • Pequena
  • Médio
  • Grande
  • Extra grande

Cada forma tem um valor padrão que pode ser substituído. No seu app, você vai usar a forma média para modificar o item de lista, mas também pode declarar outras formas. Crie um novo arquivo com o nome Shape.kt no pacote ui/theme e adicione o código para as formas:

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

Agora que você definiu as shapes, transmita-as ao MaterialTheme do M3 como fez para cores e tipografia:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes,
       content = content
   )
}

Como trabalhar com formas

Assim como nas cores e na tipografia, é possível aplicar formas a componentes do Material Design usando o MaterialTheme.shape, que oferece a instância de Shape para acessar as formas do Material Design.

Muitos componentes do Material Design já têm formas padrão, mas é possível fornecer e aplicar suas próprias formas aos componentes por meio de slots disponíveis.

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

Valores de formas padrão para todos os componentes do Material 3.Mapeamento de componentes do Material Design usando diferentes tipos de formas.

Confira o mapeamento de formas em todos os componentes na documentação sobre formas.

Outras duas formas estão disponíveis para o uso (RectangleShape e CircleShape) no Compose. A forma retangular não tem um raio de borda, e a forma circular mostra as bordas circulares completas.

Você também pode aplicar formas aos seus componentes usando Modifiers que assumem formas, por exemplo, Modifier.clip, Modifier.background e Modifier.border.

Forma da barra de apps

Queremos que a barra de apps tenha um plano de fundo com cantos arredondados:

f873392abe535494.png

A TopAppBar está usando uma Row com uma cor de plano de fundo. Para atingir o segundo plano de canto arredondado, defina a forma do plano de fundo transmitindo CircleShape ao modificador em segundo plano:

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

Forma dos itens da lista de detalhes

Na tela inicial, você está utilizando um card que usa Shape.Medium por padrão. No entanto, para nossa página de detalhes, você usou uma coluna com cor de plano de fundo. Para ter uma aparência uniforme da lista, aplique uma forma média a ela.

3412771e95a45f36.png 80ee881c41a98c2a.png

Coluna de itens de lista de detalhes sem forma no item da lista (à esquerda) e forma média na lista (à direita).

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

Agora, quando o app é executado, aparece um item de lista de tela com a forma medium.

8. Ênfase

A ênfase na interface ajuda a destacar alguns conteúdos, por exemplo, quando você quer diferenciar o título das legendas. A ênfase no M3 usa variações de cor e suas combinações. Há duas maneiras de adicionar ênfase:

  1. Usando superfície, variante de superfície e plano de fundo junto com as cores da superfície e das variantes da superfície do sistema de cores expandido do M3.

Por exemplo, a superfície pode ser usada com a variante de superfície, e vice-versa, para fornecer diferentes níveis de ênfase.

As variantes de superfície também podem ser usadas com cores de destaque para dar menos ênfase do que as cores de destaque principais, mas ainda podem ser acessadas e acompanhar a taxa de contraste.

Funções de cor de superfície, plano de fundo e variante de superfície

Funções de cor de superfície, plano de fundo e variante de superfície

  1. Usar diferentes pesos de fonte para texto. Como você viu na seção de tipografia, é possível fornecer pesos personalizados à sua escala de tipos para dar uma ênfase diferente.

Agora, atualize ReplyEmailListItem.kt para fornecer uma diferença de ênfase usando a variante de superfície. Por padrão, a cor do conteúdo do card depende do plano de fundo.

Você vai mudar a cor do elemento combinável do texto de hora e do e texto do corpo para onSurfaceVariant. Isso reduz a ênfase em relação a onContainerColors, que é aplicado por padrão a elementos combináveis do texto do título e do assunto.

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

Texto de hora e corpo com a mesma ênfase em comparação com o assunto e o título (à esquerda).

Hora e corpo com ênfase reduzida em comparação com o assunto e o título (à direita).

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant,
   overflow = TextOverflow.Ellipsis
)

Para o card de e-mail importante com o plano de fundo secondaryContainer, todas as cores de texto são onSecondaryContainer por padrão. Para os outros e-mails, o plano de fundo é surfaceVariant,. Portanto, todo o texto tem onSurfaceVariant como padrão.

9. Parabéns

Parabéns! Você concluiu este codelab. Você implementou a aplicação de temas do Material Design no Compose usando cores, tipografia e formas. Além disso, usou cores dinâmicas para definir o tema do aplicativo e oferecer uma experiência personalizada.

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

Fim dos resultados da aplicação de temas com cores dinâmicas e tema de cores aplicados.

A seguir

Confira nossos outros codelabs no Programa de treinamentos do Compose:

Leia mais

Apps de exemplo

Documentos de referência