Material 3 e Jetpack Compose: Entendendo o Sistema de Cores
Você já tentou mudar as cores no Jetpack Compose com Material 3 e percebeu que às vezes parece até um bug? Você faz as alterações e nada acontece? Comigo acontece também! Neste artigo, vou te mostrar como corrigir essas situações e como entender realmente o funcionamento do sistema de cores dentro do Material 3 no Jetpack Compose.
Criando uma Tela de Exemplo
Vamos começar criando uma tela simples para demonstrar os conceitos. Aqui está um exemplo básico de uma HomeScreen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun HomeScreen() {
// Box é um container que permite empilhar elementos
Box(
modifier = Modifier.fillMaxSize(), // Preenche toda a tela
contentAlignment = Alignment.Center // Centraliza o conteúdo
) {
Button(
onClick = {
// Aqui você pode adicionar a ação do botão
}
) {
Text("Olá") // Texto simples dentro do botão
}
}
}
@Preview
@Composable
fun HomeScreenPreview() {
HomeScreen() // Chama nossa tela para visualização no Preview
}
O Mistério das Cores Diferentes
Ao executar este código, você pode notar algo estranho: no Preview, o botão aparece azul, mas no smartphone real ele fica roxo. Por que essa diferença acontece?
Entendendo o Sistema de Temas
Quando você cria um projeto novo no Android Studio, ele automaticamente cria uma função de tema com o nome do seu projeto seguido de “Theme”. Esta função está localizada no arquivo Theme.kt
dentro do pacote ui.theme
.
Primeiro, precisamos entender como funciona o sistema de cores dentro do Jetpack Compose. Toda vez que você cria um projeto novo, ele já constrói para você uma função com o nome do seu projeto e o contexto de tema no final.
O que é essa função?
Se abrirmos esse pacote ui.theme
e abrirmos o arquivo Theme.kt
, temos algumas definições de cores e também de comportamentos para o funcionamento do Android usando o fundamento do Material Design.
O Material Design trabalha com dois pilares fundamentais:
- Cores - Definidas no sistema de cores
- Tipografia - As fontes que serão utilizadas
Por isso que quando você cria um projeto já vem com esses dois elementos: cores e type. Se olharmos no arquivo do tema em si, temos definições para dois temas: o tema claro e o tema escuro. Então vai depender se você tiver ativado no seu smartphone o modo escuro ou o modo claro.
Dynamic Colors: O Culpado da Confusão
A diferença de cores entre Preview e dispositivo real acontece por causa do Dynamic Color - um recurso introduzido no Android 12 (API 31+).
Aqui no preview não tem um papel de parede especificamente, então ele usa o padrão do smartphone, que é de um emulador qualquer. Os emuladores geralmente vêm com aquele papel de parede mais azulado, e é por isso que nosso botão está azul. Se rodar isso no emulador de verdade, criar um emulador e mudar as cores do papel de parede no emulador, será possível ver que isso vai ser aplicado automaticamente.
No arquivo Theme.kt
, você encontrará algo assim:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Composable
fun YourAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(), // Detecta automaticamente se está em modo escuro
dynamicColor: Boolean = true, // Este parâmetro controla as cores dinâmicas
content: @Composable () -> Unit
) {
val colorScheme = when {
// Se dynamic color está ativado E o Android é versão 12+ (API 31+)
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
// Aplica cores baseadas no papel de parede do sistema
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
// Se está em modo escuro, usa o esquema escuro personalizado
darkTheme -> DarkColorScheme
// Caso contrário, usa o esquema claro personalizado
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
O que é Dynamic Color?
Dynamic Color é um sistema que adapta as cores do seu app baseado no papel de parede do dispositivo. Se tiver um wallpaper amarelo, as chances são de que os elementos do app ganhem tons amarelados. É uma funcionalidade interessante para criar harmonia visual entre o sistema e os aplicativos, mas pode causar surpresas durante o desenvolvimento.
O problema é que às vezes você está programando, acha que o botão vai ficar azul, mas no fim do dia ele acaba ficando roxo. Isso acontece porque o Dynamic Color só funciona em dispositivos com Android 12 ou superior (API 31+). Se estiver testando num dispositivo com Android mais antigo, ele não vai aplicar essas cores dinâmicas.
Para verificar a versão do Android que está rodando, você pode usar:
1
2
// Exibe a versão do SDK para debug e verificação
Text("SDK Version: ${Build.VERSION.SDK_INT}")
Controlando as Cores Manualmente
Desabilitando Dynamic Color
Se você quiser ter controle total sobre as cores e evitar surpresas, desabilite o Dynamic Color:
1
2
3
4
5
YourAppTheme(
dynamicColor = false // Desabilita as cores dinâmicas para ter controle total
) {
HomeScreen()
}
Definindo Cores Personalizadas
No arquivo Color.kt
, é possível definir suas próprias cores. Aqui vai uma dica importante que eu sempre dou: defina cores bem “estranhas” e distintas durante o desenvolvimento para conseguir enxergar o comportamento delas. É muito mais fácil identificar onde cada cor está sendo aplicada quando usar um vermelho bem chamativo, um verde bem vibrante, um azul bem destacado.
1
2
3
4
5
6
7
8
9
// Defina cores personalizadas com valores hexadecimais
// Use cores bem distintas durante desenvolvimento para identificar facilmente onde são aplicadas
val MyRed = Color(0xFFE91E63) // Vermelho para elementos primários
val Green = Color(0xFF4CAF50) // Verde para identificar elementos secundários
val MyBlue = Color(0xFF2196F3) // Azul para elementos terciários
val MyYellow = Color(0xFFFFEB3B) // Amarelo para modo escuro
val MyMagenta = Color(0xFFE91E63) // Magenta para modo escuro
val MyCyan = Color(0xFF00BCD4) // Ciano para modo escuro
Depois, quando estiver tudo funcionando, é só trocar pelas cores finais do design.
Aplicando as Cores nos Temas
No arquivo Theme.kt
, defina esquemas de cores distintos para modo claro e escuro:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Esquema de cores para modo claro
private val LightColorScheme = lightColorScheme(
primary = MyRed, // Cor principal (botões, elementos importantes)
secondary = Green, // Cor secundária (elementos de apoio)
tertiary = MyBlue, // Cor terciária (detalhes, acentos)
background = Purple40 // Cor de fundo da aplicação
)
// Esquema de cores para modo escuro
private val DarkColorScheme = darkColorScheme(
primary = MyYellow, // Cores mais suaves para modo escuro
secondary = MyMagenta, // Evita cansaço visual em ambientes com pouca luz
tertiary = MyCyan,
background = Purple80 // Fundo mais escuro
)
Aplicando Cores de Fundo
Agora vamos à segunda situação que acontece muito quando a gente faz um aplicativo: definir a cor de fundo. Você pode estar pensando “ah, é só colocar um background e pronto”, mas tem algumas pegadinhas importantes aqui.
Método 1: Diretamente no Componente
1
2
3
4
5
6
7
8
9
Box(
modifier = Modifier
.fillMaxSize()
.background(Green), // Aplicando cor diretamente no componente
contentAlignment = Alignment.Center
) {
// Esta abordagem funciona, mas não é a mais recomendada
// porque você precisa repetir a cor em cada tela
}
A primeira ideia seria colocar o background
nos componentes.
Só que se tivermos, por exemplo, 20, 30 telas, como fazemos para aplicar isso de uma maneira direta, sem ter essa necessidade de ficar colocando a cor toda hora?
Podemos evitar ficar duplicando esse trecho de código. Porque, por exemplo, se eu colocar o background green em todas as telas, e todas as telas são verdes, e aí amanhã eu mudo a tela para roxo, eu vou ter que procurar todos os códigos para fazer essa mudança.
A maneira mais inteligente seria usar o Material 3 para envelopar esse conceito de cores num lugar só.
Método 2: Usando o Sistema de Temas (Recomendado)
1
2
3
4
5
6
7
8
9
10
11
Box(
modifier = Modifier
.fillMaxSize()
// Usa a cor de fundo definida no tema atual
// Automaticamente muda entre modo claro/escuro
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
// Esta é a abordagem recomendada!
// Centraliza as definições e facilita manutenção
}
Então, se eu tiver, por exemplo, 20 telas, posso estar aplicando sempre o mesmo sistema de cores, usando MaterialTheme.colorScheme.background
. E aí, eu mudo lá no esquema, que é o arquivo de cores e será aplicado em todo o meu aplicativo.
O segundo método é preferível porque:
- Centraliza as definições de cor
- Facilita mudanças globais
- Mantém consistência visual
- Respeita o modo claro/escuro automaticamente
O Papel do Scaffold
Agora, vamos supor que eu não quero definir esse background toda hora. Por padrão, tanto o Box
quanto o Column
, que são containers que temos, ou o Row
, que são linhas também, por padrão, eles não poussem uma definição de background color. Eles não pegam automaticamente a cor do tema.
Mas tem um outro componente que estamos utilizando que é o Scaffold
. O Scaffold, ele pega as cores padrões automaticamente. Então, se eu remover o background, você irá perceber que no preview pode não aplicar ainda (ele fica branco), mas se eu rodar no meu smartphone, como ele está envelopado dentro desse conteúdo chamado Scaffold, você verá que ele terá a cor background que eu tinha definido no tema.
Por isso que temos essas inconsistências de cores, onde você não entende de onde está vindo qual cor.
Alguns componentes seguem padrões nativos do Android, aplicando cores baseadas nos fundamentos do Material Design. Por isso, é importante entender que o comportamento visual pode diferir entre a execução no smartphone e a visualização no preview.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Composable
fun MyScreen() {
// Scaffold é um componente que fornece estrutura básica para telas
Scaffold { paddingValues ->
// O Scaffold automaticamente aplica:
// - Cor de fundo do tema
// - Padding para evitar sobreposição com status bar
// - Estrutura para TopBar, BottomBar, FAB, etc.
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues) // Respeita as áreas do sistema
) {
// Seu conteúdo aqui já terá o fundo correto automaticamente
}
}
}
Testando no Preview vs Dispositivo Real
Para ter uma visualização mais precisa no Preview, configure explicitamente os parâmetros do tema. Sempre defina esses parâmetros com cores aleatórias, para entender aonde está sendo aplicado. Onde realmente o sistema do Android aplica essas definições, e como é possível entender a fundo, explorando mesmo como o Material Design funciona.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Preview
@Composable
fun HomeScreenPreview() {
YourAppTheme(
darkTheme = false, // Força modo claro para preview consistente
dynamicColor = false // Remove variações de cor por wallpaper
) {
// Envolver com Scaffold para ter visualização real
Scaffold {
HomeScreen()
}
}
}
Dessa forma, o que você vê no Preview será exatamente o que aparecerá no dispositivo real, eliminando surpresas na hora de testar o app. Se eu pegar aqui, por exemplo, e colocar também o Scaffold no meu preview, aí sim vamos estar vendo a cor real que definimos, que é a cor roxa, usando o conceito do Material Design, lá no arquivo de temas com a propriedade do background.
Então, se eu comentar o Scaffold, como eu falei, ele volta à cor padrão. Aí, se eu quiser realmente usar uma cor de fundo, usando o tema, e eu não estou envelopando nada dentro do Scaffold, aí sim eu sou obrigado a usar um background manual, como já mostrei anteriormente, definindo qual esquema com background.
Melhores Práticas
- Sempre use cores do tema em vez de cores hardcoded
- Desabilite Dynamic Color durante o desenvolvimento para ter controle total
- Defina cores contrastantes temporariamente para entender onde cada cor é aplicada
- Use Scaffold quando quiser que o fundo siga automaticamente o tema
- Teste sempre em dispositivos reais além do Preview
Conclusão
Entender o sistema de cores do Material 3 no Jetpack Compose pode parecer complexo inicialmente, mas seguindo essas práticas você terá controle total sobre a aparência do seu app.
Já caiu nessa armadilha, nessa pegadinha do Material Design 3?
É muito comum isso acontecer, principalmente quando estamos começando com Jetpack Compose.
A questão é que às vezes parece até um bug ou então aparece uma cor completamente diferente do que você esperava. Mas agora você entende que isso não é um bug, é o comportamento padrão do sistema de Dynamic Colors e das diferenças entre como os componentes aplicam as cores do tema.
Lembre-se: a consistência visual é fundamental para uma boa experiência do usuário, e dominar o sistema de cores do Material 3 é essencial para criar interfaces modernas e profissionais no Android. Com essas técnicas, você vai conseguir criar layouts mais responsivos e elegantes dentro dessa tecnologia do Android.
Gostou do conteúdo? Estas técnicas fazem parte de um desenvolvimento Android moderno e profissional usando Jetpack Compose, seguindo todas as boas práticas e diretrizes atuais da plataforma.
Se quiser se aprofundar em Jetpack Compose, considere meu treinamento completo do zero ao pro, o Android Compose.
Maravilha! Em breve você receberá conteúdos exclusivos por e-mail. Continue lendo os artigos para aprender mais sobre programação.