1. Antes de começar
O Material Design é um sistema de design criado e aceito por designers e desenvolvedores do Google para criar experiências digitais de alta qualidade no Android e em outras plataformas da Web e de dispositivos móveis. Ele apresenta diretrizes sobre como criar a interface do app de uma forma legível, atraente e consistente.
Neste codelab, você vai aprender sobre os Temas do Material Design, que permitem usar esse sistema no seu app, com orientações sobre personalização de cores, tipografia e formas. É possível personalizar o quanto quiser. Você também vai aprender a adicionar uma barra de apps na parte de cima para mostrar o nome e o ícone do app.
Pré-requisitos
- Familiaridade com a linguagem Kotlin, incluindo sintaxe, funções e variáveis.
- Saber criar layouts no Compose, incluindo linhas e colunas com padding.
- Saber criar listas simples no Compose.
O que você vai aprender
- Como aplicar os temas do Material Design a um app do Compose.
- Como adicionar uma paleta de cores personalizada ao app.
- Como adicionar fontes personalizadas ao app.
- Como adicionar formas personalizadas a elementos no app.
- Como adicionar uma barra de apps à parte de cima do aplicativo.
O que você vai criar
- Você vai criar um belo app que incorpora as práticas recomendadas do Material Design.
O que é necessário
- Ter a versão mais recente do Android Studio.
- Uma conexão de Internet para fazer o download do código inicial e das fontes.
2. Visão geral do app
Neste codelab, você vai criar o Woof, um app que mostra uma lista de cachorros e usa o Material Design para criar uma bela experiência.
Neste codelab, vamos mostrar algumas das possibilidades usando temas do Material Design. Use este codelab para ter ideias sobre o uso dos temas do Material Design e melhorar a aparência dos apps que você criar.
Paleta de cores
Confira abaixo as paletas de cores para os temas claro e escuro que vamos criar.
Este é o app final nos temas claro e escuro.
Tema claro | Tema escuro |
Tipografia
Confira abaixo os estilos de tipografia que você vai usar no app.
Arquivo de tema
O arquivo Theme.kt contém todas as informações sobre o tema do app, definidas por cor, tipografia e forma. Esse é um arquivo importante que você precisa conhecer. Dentro dele está o WoofTheme()
de composição, que define as cores, a tipografia e as formas do app.
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
setUpEdgeToEdge(view, darkTheme)
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
/**
* Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
* light or dark depending on whether the [darkTheme] is enabled or not.
*/
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
val navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
// Min sdk version for this app is 24, this block is for SDK versions 24 and 25
else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
}
window.navigationBarColor = navigationBarColor
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !darkTheme
controller.isAppearanceLightNavigationBars = !darkTheme
}
Em MainActivity.kt, o WoofTheme()
é adicionado para fornecer os temas do Material Design para todo o app.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WoofTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
WoofApp()
}
}
}
}
}
Dê uma olhada na WoofPreview()
. O WoofTheme()
foi adicionado para fornecer os temas do Material Design mostrados no WoofPreview()
.
@Preview
@Composable
fun WoofPreview() {
WoofTheme(darkTheme = false) {
WoofApp()
}
}
3. Acessar o código inicial
Para começar, faça o download do código inicial:
Outra opção é clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout starter
Procure o código no repositório do GitHub do Woof app
(link em inglês).
Analisar o código inicial
- Abra o código inicial no Android Studio.
- Abra com.example.woof > data > Dog.kt. O arquivo contém a classe
Dog data class
que vai ser usada para representar a foto, o nome, a idade e os hobbies do cachorro. Ele também contém uma lista de cachorros e as informações que você vai usar como dados no app. - Abra res > drawable. Ele contém todos os recursos de imagem necessários para este projeto, incluindo o ícone do app e os ícones e imagens de cachorros.
- Abra res > values > strings.xml. Ele contém as strings que você vai usar no app, incluindo o nome do aplicativo, os nomes dos cachorros, as descrições e muito mais.
- Abra MainActivity.kt. Ele contém o código para criar uma lista simples com a foto de um cachorro, o nome e a idade dele.
WoofApp()
contém umaLazyColumn
que exibe osDogItem
s.DogItem()
contém umaRow
que mostra uma foto do cachorro e informações sobre ele.DogIcon()
mostra uma foto do cachorro.DogInformation()
mostra o nome e a idade do cachorro.WoofPreview()
mostra uma prévia do app no painel Design.
Conferir se o emulador/dispositivo está no tema claro
Neste codelab, você vai trabalhar com os temas claro e escuro, mas a maior parte do guia foca no tema claro. Antes de começar, confira se o dispositivo/emulador está no tema claro.
Para conferir o app no tema claro, siga estas etapas no emulador ou dispositivo físico:
- Acesse o app Configurações.
- Pesquise tema escuro e clique nessa opção.
- Desative o tema escuro se ele estiver ativado.
Execute o código inicial para conferir como as coisas estão no começo. O app é uma lista que mostra cachorros, com fotos, nomes e idades. Ele funciona, mas não tem uma aparência muito boa. Vamos cuidar disso.
4. Adicionar cores
A primeira coisa que você vai modificar no app Woof é o esquema de cores.
Um esquema de cores é a combinação de cores que o app usa. Combinações diferentes evocam sensações diferentes, o que influencia a reação das pessoas ao usar o app.
No sistema Android, a cor é representada por um valor hexadecimal (hex). Um código de cor hexadecimal começa com uma cerquilha (#) e é seguido por seis letras e/ou números que representam os componentes vermelho, verde e azul (RGB) dessa cor. As duas primeiras letras/números se referem ao vermelho, as duas seguintes ao verde e as duas últimas ao azul.
Uma cor também pode incluir um valor alfa (letras e/ou números) representando a transparência: #00 indica 0% de opacidade, ou seja, totalmente transparente, e #FF indica 100% de opacidade, ou seja, totalmente opaco. Quando incluído, o valor alfa é composto pelos primeiros dois caracteres do código de cor hexadecimal após o caractere cerquilha (#). Se não houver um valor alfa, ele vai ser considerado #FF, que indica 100% de opacidade (totalmente opaco).
Confira abaixo alguns exemplos de cores e seus valores hexadecimais.
Usar o Material Theme Builder para criar um esquema de cores
Para criar um esquema de cores personalizado para o nosso app, vamos usar o Material Theme Builder.
- Clique neste link para acessar o Material Theme Builder (link em inglês).
- As cores primárias estão no painel esquerdo. Clique em "Primary":
- O seletor de cores do HCT será aberto.
- Para criar o esquema de cores mostrado nas capturas de tela do app, mude a cor principal no seletor de cores. Na caixa de texto, substitua o texto atual por #006C4C. Assim, a cor principal do app vai ficar verde.
Observe que isso atualiza os apps na tela adotando um esquema de cores verde.
- Role a página para baixo para conferir o esquema completo de cores para os temas claro e escuro gerados a partir da cor inserida.
Você pode se perguntar quais são essas funções e como elas são utilizadas. Confira algumas das principais:
- As cores primárias são usadas para os componentes mais importantes da interface.
- As cores secundárias são usadas para componentes menos proeminentes na interface.
- As cores terciárias são usadas para dar destaque a contrastes que podem ser usados para equilibrar cores primárias e secundárias ou chamar a atenção para um elemento, como um campo de entrada.
- Os elementos de cor on aparecem sobre as outras cores da paleta e são aplicados principalmente a textos, iconografia e traços. Temos também uma cor onSurface, que aparece por cima da cor de superfície, e uma cor onPrimary, que aparece por cima da cor principal.
Esses slots oferecem um sistema de design coeso em que os componentes relacionados são coloridos de forma semelhante.
Mas chega de falar de teoria. Hora de adicionar essa bela paleta de cores ao app.
Adicionar a paleta de cores ao tema
Na página do Material Theme Builder, há a opção de clicar no botão Export para fazer o download de um arquivo Color.kt e Theme.kt com o tema personalizado que você criou.
Isso vai funcionar para adicionar o tema personalizado que criamos ao seu app. No entanto, como o arquivo Theme.kt gerado não inclui o código de cor dinâmica que vamos abordar mais adiante no codelab, copie os arquivos.
- Abra o arquivo Color.kt e substitua o conteúdo pelo código abaixo para copiar no novo esquema de cores.
package com.example.woof.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006C4C)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF89F8C7)
val md_theme_light_onPrimaryContainer = Color(0xFF002114)
val md_theme_light_secondary = Color(0xFF4D6357)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCFE9D9)
val md_theme_light_onSecondaryContainer = Color(0xFF092016)
val md_theme_light_tertiary = Color(0xFF3D6373)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB)
val md_theme_light_onTertiaryContainer = Color(0xFF001F29)
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(0xFFFBFDF9)
val md_theme_light_onBackground = Color(0xFF191C1A)
val md_theme_light_surface = Color(0xFFFBFDF9)
val md_theme_light_onSurface = Color(0xFF191C1A)
val md_theme_light_surfaceVariant = Color(0xFFDBE5DD)
val md_theme_light_onSurfaceVariant = Color(0xFF404943)
val md_theme_light_outline = Color(0xFF707973)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)
val md_theme_light_inverseSurface = Color(0xFF2E312F)
val md_theme_light_inversePrimary = Color(0xFF6CDBAC)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006C4C)
val md_theme_light_outlineVariant = Color(0xFFBFC9C2)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF6CDBAC)
val md_theme_dark_onPrimary = Color(0xFF003826)
val md_theme_dark_primaryContainer = Color(0xFF005138)
val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7)
val md_theme_dark_secondary = Color(0xFFB3CCBE)
val md_theme_dark_onSecondary = Color(0xFF1F352A)
val md_theme_dark_secondaryContainer = Color(0xFF354B40)
val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9)
val md_theme_dark_tertiary = Color(0xFFA5CCDF)
val md_theme_dark_onTertiary = Color(0xFF073543)
val md_theme_dark_tertiaryContainer = Color(0xFF244C5B)
val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB)
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(0xFF191C1A)
val md_theme_dark_onBackground = Color(0xFFE1E3DF)
val md_theme_dark_surface = Color(0xFF191C1A)
val md_theme_dark_onSurface = Color(0xFFE1E3DF)
val md_theme_dark_surfaceVariant = Color(0xFF404943)
val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2)
val md_theme_dark_outline = Color(0xFF8A938C)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)
val md_theme_dark_inversePrimary = Color(0xFF006C4C)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF6CDBAC)
val md_theme_dark_outlineVariant = Color(0xFF404943)
val md_theme_dark_scrim = Color(0xFF000000)
- Abra o arquivo Theme.kt e substitua o conteúdo pelo código abaixo para adicionar as novas cores ao tema.
package com.example.woof.ui.theme
import android.app.Activity
import android.os.Build
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
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 WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
setUpEdgeToEdge(view, darkTheme)
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
/**
* Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
* light or dark depending on whether the [darkTheme] is enabled or not.
*/
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
val navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
// Min sdk version for this app is 24, this block is for SDK versions 24 and 25
else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
}
window.navigationBarColor = navigationBarColor
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !darkTheme
controller.isAppearanceLightNavigationBars = !darkTheme
}
Em WoofTheme()
, o colorScheme val
usa uma instrução when
.
- Se
dynamicColor
for verdadeiro e a versão do build for S ou mais recente, ela vai verificar se o dispositivo está emdarkTheme
ou não. - Se ele estiver no tema escuro, o
colorScheme
será definido comodynamicDarkColorScheme
. - Se ele não estiver no tema escuro, será definido como
dynamicLightColorScheme
. - Se o app não estiver usando
dynamicColorScheme
, ele vai verificar se seu app está emdarkTheme
. Nesse caso,colorScheme
será definido comoDarkColors
. - Se nenhuma dessas opções for verdadeira,
colorScheme
será definido comoLightColors
.
O arquivo copiado em Theme.kt tem dynamicColor
definido como falso e os dispositivos com os quais estamos trabalhando estão no modo claro. Portanto, colorScheme
será definido como LightColors
.
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
- Execute o app novamente. Observe que a barra de apps mudou de cor automaticamente.
Mapeamento de cores
Os componentes do Material Design são mapeados automaticamente para os slots de cor. Outros componentes importantes da interface, como os botões de ação flutuantes, também são padronizados com a cor principal. Isso significa que você não precisa atribuir explicitamente uma cor a um componente. Ele será automaticamente mapeado para um slot quando você definir o tema de cores no app. Você pode substituir isso definindo explicitamente uma cor no código. Leia mais sobre as funções de cor neste link.
Nesta seção, vamos unir a Row
que contém o DogIcon()
e DogInformation()
com um Card
para diferenciar as cores dos itens da lista com o plano de fundo.
- Na função combinável
DogItem()
, envolva aRow()
com umCard()
.
Card() {
Row(
modifier = modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
}
}
- Como o
Card
agora é o primeiro elemento combinável filho emDogItem()
, transmita o modificador deDogItem()
paraCard
e atualize o modificador deRow
para uma nova instância deModifier
.
Card(modifier = modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
}
}
- Dê uma olhada no arquivo
WoofPreview()
. A cor dos itens da lista mudou automaticamente devido aos elementos combináveisCard
. As cores parecem ótimas, mas não há espaçamento entre os itens da lista.
Arquivo dimens
Assim como você usa o arquivo strings.xml para armazenar as strings no seu app, também é uma prática recomendada usar um arquivo chamado dimens.xml para armazenar valores de dimensão. Isso é útil para não fixar valores no código e, se necessário, você pode mudá-los em um único local.
Acesse app > res > values > dimens.xml e observe o arquivo. Ele armazena valores de dimensão para padding_small
, padding_medium
e image_size
. Essas dimensões serão usadas em todo o app.
<resources>
<dimen name="padding_small">8dp</dimen>
<dimen name="padding_medium">16dp</dimen>
<dimen name="image_size">64dp</dimen>
</resources>
Para adicionar um valor a partir do arquivo dimens.xml, este é o formato correto:
Por exemplo, para adicionar padding_small
, transmita dimensionResource(id = R.dimen.
padding_small
)
.
- Em
WoofApp()
, adicione ummodifier
compadding_small
na chamada paraDogItem()
.
@Composable
fun WoofApp() {
Scaffold { it ->
LazyColumn(contentPadding = it) {
items(dogs) {
DogItem(
dog = it,
modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
)
}
}
}
}
Agora há mais definição entre itens de lista em WoofPreview()
.
Tema escuro
No sistema Android, você tem a opção de colocar o dispositivo no tema escuro. Um tema escuro usa cores mais escuras e discretas e:
- Pode reduzir significativamente o consumo de energia (dependendo da tecnologia da tela do dispositivo).
- Melhora a visibilidade para usuários com problemas de visão e que tenham sensibilidade ao brilho da luz.
- Facilita o uso do dispositivo em ambientes com pouca luz.
O app pode ativar o recurso Forçar modo escuro, que faz com que o sistema implemente um tema escuro para você. No entanto, os usuários vão ter uma experiência melhor se você implementar o tema escuro no app. Assim, você tem controle total sobre o tema do app.
Ao criar um tema escuro próprio, é importante lembrar que as cores dele precisam atender aos padrões de contraste de acessibilidade (link em inglês). Os temas escuros usam uma cor de superfície escura com tons de cores limitados.
Conferir o tema escuro na visualização
Você já adicionou as cores ao tema escuro na etapa anterior. Para conferir o tema escuro, adicione outro elemento combinável da prévia em MainActivity.kt. Dessa forma, ao mudar o layout da interface no seu código, você vai poder conferir a aparência do tema claro e do tema escuro simultaneamente.
- Em
WoofPreview()
, crie uma nova função com o nomeWoofDarkThemePreview()
e faça uma anotação com@Preview
e@Composable
.
@Preview
@Composable
fun WoofDarkThemePreview() {
}
- Em
DarkThemePreview()
, adicione oWoofTheme()
. Se o métodoWoofTheme()
não fosse adicionado, nenhum estilo que adicionamos apareceria no app. Defina o parâmetrodarkTheme
como true.
@Preview
@Composable
fun WoofDarkThemePreview() {
WoofTheme(darkTheme = true) {
}
}
- Chame
WoofApp()
emWoofTheme()
.
@Preview
@Composable
fun WoofDarkThemePreview() {
WoofTheme(darkTheme = true) {
WoofApp()
}
}
Role para baixo no painel Design para conferir o app no tema escuro, incluindo o plano de fundo mais escuro do app/item da lista e o texto mais claro. Compare as diferenças entre os temas claro e escuro.
Tema escuro | Tema claro |
Conferir o tema escuro no dispositivo ou emulador
Para visualizar o app no tema escuro no emulador ou dispositivo físico, siga estas etapas:
- Acesse o app Configurações.
- Pesquise tema escuro e clique nessa opção.
- Ative o Tema escuro.
- Abra o app Woof novamente com o tema escuro ativado.
Este codelab se concentra mais no tema claro. Portanto, antes de avançar, desative o tema escuro.
- Acesse o app Configurações.
- Selecione Tela.
- Desative o Tema escuro.
Compare a aparência do app quando começamos e a de agora. Os itens e texto da lista estão mais definidos, e o esquema de cores está mais bonito.
Sem cor | Com cor (tema claro) | Com cor (tema escuro) |
Cores dinâmicas
O Material Design 3 é altamente focado na personalização do usuário. Um novo recurso é a cor dinâmica, que cria um tema para o app com base no plano de fundo do usuário. Dessa forma, se o usuário adora verde e tem um plano de fundo azul no smartphone, o app Woof também vai estar em azul para refletir isso. O tema dinâmico está disponível apenas em determinados dispositivos com o Android 12 ou versões mais recentes.
Um tema personalizado pode ser usado para apps com cores fortes de marca e que também precisa ser implementado em dispositivos que não oferecem suporte a temas dinâmicos, para que o app ainda tenha um tema.
- Para ativar a cor dinâmica, abra Theme.kt, acesse o elemento combinável
WoofTheme()
e defina o parâmetrodynamicColor
como verdadeiro.
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
)
- Para mudar o plano de fundo de um dispositivo ou emulador, acesse as Configurações e pesquise Plano de fundo.
- Mude o plano de fundo para uma cor ou conjunto de cores.
- Execute o app novamente para conferir o tema dinâmico (observe que o dispositivo ou emulador precisa ser o Android 12 ou uma versão mais recente). Fique à vontade para testar isso com planos de fundo diferentes.
- Este codelab se concentra em temas personalizados. Portanto, desative
dynamicColor
antes de continuar.
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = false,
content: @Composable () -> Unit
)
5. Adicionar formas
No entanto, aplicar uma forma pode mudar muito a aparência de um elemento combinável. As formas podem ser usadas para chamar atenção, identificar os componentes, comunicar o estado e expressar a marca.
Muitas formas são definidas usando RoundedCornerShape
, que descreve um retângulo com cantos arredondados. O número transmitido define o quão redondos os cantos vão ser. Se RoundedCornerShape(0.dp)
for usado, o retângulo não vai ter cantos arredondados. Se RoundedCornerShape(50.dp)
for usado, os cantos vão ser totalmente circulares.
0 dp | 25 dp | 50 dp |
Você também pode personalizar ainda mais as formas adicionando diferentes porcentagens de arredondamento em cada canto. É muito divertido brincar com as formas.
Canto superior esquerdo: 50 dp | Canto superior esquerdo: 15 dp | Canto superior esquerdo: 0 dp |
O arquivo Shape.kt é usado para definir formas de componentes no Compose. Existem três tipos de componentes: pequeno, médio e grande. Nesta seção, você vai modificar o componente Card
, que é definido por um tamanho medium
. Os componentes são agrupados em categorias de formas (link em inglês) com base no tamanho.
Nesta seção, você vai mudar a forma da imagem do cachorro para um círculo e modificar a forma do item da lista.
Transformar a imagem do cachorro em um círculo
- Abra o arquivo Shape.kt e observe que o parâmetro pequeno está definido como
RoundedCornerShape(50.dp)
. Ele será usado para moldar a imagem em um círculo.
val Shapes = Shapes(
small = RoundedCornerShape(50.dp),
)
- Abra MainActivity.kt. Em
DogIcon()
, adicione um atributoclip
aomodifier
daImage
. Isso vai cortar a imagem em uma forma específica. Transmita oMaterialTheme.shapes.small
.
import androidx.compose.ui.draw.clip
@Composable
fun DogIcon(
@DrawableRes dogIcon: Int,
modifier: Modifier = Modifier
) {
Image(
modifier = modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small))
.clip(MaterialTheme.shapes.small),
Ao observar a WoofPreview()
, você vai perceber que os ícones dos cachorros agora são circulares. No entanto, algumas fotos estão cortadas nas laterais e não aparecem como círculos.
- Para tornar todas as fotos circulares, adicione atributos
ContentScale
eCrop
. A imagem será cortada para caber no espaço disponível. Observe quecontentScale
é um atributo daImage
e não faz parte demodifier
.
import androidx.compose.ui.layout.ContentScale
@Composable
fun DogIcon(
@DrawableRes dogIcon: Int,
modifier: Modifier = Modifier
) {
Image(
modifier = modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small))
.clip(MaterialTheme.shapes.small),
contentScale = ContentScale.Crop,
Este é o elemento combinável DogIcon()
completo.
@Composable
fun DogIcon(
@DrawableRes dogIcon: Int,
modifier: Modifier = Modifier
) {
Image(
modifier = modifier
.size(dimensionResource(R.dimen.image_size))
.padding(dimensionResource(R.dimen.padding_small))
.clip(MaterialTheme.shapes.small),
contentScale = ContentScale.Crop,
painter = painterResource(dogIcon),
// Content Description is not needed here - image is decorative, and setting a null content
// description allows accessibility services to skip this element during navigation.
contentDescription = null
)
}
Agora, em WoofPreview()
, os ícones são circulares.
Adicionar uma forma ao item da lista
Nesta seção, você vai adicionar uma forma ao item da lista. O item da lista já está sendo exibido por um Card
. Um Card
é uma superfície que pode conter um único elemento combinável, além de opções para decoração. A decoração pode ser adicionada pela borda, forma e muito mais. Nesta seção, você vai usar o Card
para adicionar uma forma ao item da lista.
- Abra o arquivo Shape.kt. Um
Card
é um componente médio, então você adiciona o parâmetro médio do objetoShapes
. Para este app, os cantos superior direito e inferior esquerdo do item da lista, mas não os tornam totalmente circulares. Para fazer isso, transmita16.dp
ao atributomedium
.
medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
Como um Card
, por padrão, já usa a forma média, você não precisa fazer uma definição explícita. Confira a Prévia e o Card
com a nova forma.
Ao retornar ao arquivo Theme.kt no WoofTheme()
e analisar o MaterialTheme()
, você vai notar que o atributo shapes
está definido como o val
Shapes
que você acabou de atualizar.
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
Confira abaixo os itens da lista lado a lado antes e depois da modelagem. Observe como ele ficou mais bonito.
Sem formas | Com formas |
6. Adicionar tipografia
A escala de tipografia do Material Design
Uma escala de tipografia é uma seleção de estilos de fonte que podem ser usados em um app, garantindo um estilo flexível e consistente. A escala de tipografia do Material Design (link em inglês) inclui 15 estilos de fonte com suporte ao sistema de tipografia. A nomenclatura e o agrupamento foram simplificados para: display, headline, title, body e label, com tamanhos grandes, médios e pequenos para cada um. Você só precisa usar essas opções caso queira personalizar o app. Se você não souber o que definir para cada categoria da escala, há uma escala de tipografia padrão que pode ser usada.
A escala de tipografia contém categorias reutilizáveis de texto, cada uma com uma intenção de aplicação e significado.
Tela
Como o maior texto na tela, os estilos de "display" são reservados para textos ou números curtos e importantes. Eles funcionam melhor em telas grandes.
Título
"Headlines" são mais adequadas para textos curtos e de alta ênfase em telas menores. Esses estilos podem ser bons para marcar trechos do texto ou regiões de conteúdo importantes.
Title
Os "titles" são menores do que os estilos de "headlines" e devem ser usados para textos de prioridade média que permaneçam relativamente curtos.
Body
Os estilos de "body" são usados para trechos mais longos de texto no seu app.
Label
"Labels" são estilos utilitários menores, usados para elementos como o texto dentro de componentes ou para textos muito pequenos no corpo do conteúdo, como legendas.
Fontes
A plataforma Android oferece várias fontes, mas você pode personalizar seu app com uma fonte não fornecida por padrão. Fontes personalizadas podem adicionar personalidade e ser usadas para representar sua marca.
Nesta seção, você vai adicionar fontes personalizadas conhecidas como Abril Fatface, Montserrat Bold e Montserrat Regular. Você vai usar os títulos displayLarge e displayMedium e o texto bodyLarge do sistema de tipografia do Material Design para adicionar ao texto no app.
Criar uma fonte no diretório de recursos do Android.
Antes de adicionar fontes ao app, você vai precisar adicionar um diretório de fontes.
- Na visualização de projeto do Android Studio, clique com o botão direito do mouse na pasta res.
- Selecione New > Android Resource Directory.
- Nomeie o diretório como font, defina o tipo de recurso como font e clique em OK.
- Abra o novo diretório de recursos de fontes localizado em res > font.
Fazer o download de fontes personalizadas
Como você está usando fontes que não são fornecidas pela Plataforma Android, vai ser necessário fazer o download de fontes personalizadas.
- Acesse https://fonts.google.com/.
- Pesquise Montserrat e clique em Download family.
- Descompacte o arquivo ZIP.
- Abra a pasta Montserrat que você transferiu por download. Na pasta static, localize Montserrat-Bold.ttf e Montserrat-Regular.ttf (ttf significa TrueType Font e é o formato dos arquivos de fontes). Selecione os dois arquivos e arraste para o diretório de recursos de fontes do projeto no Android Studio.
- Na pasta de fontes, renomeie Montserrat-Bold.ttf como montserrat_bold.ttf e Montserrat-Regular.ttf como montserrat_regular.ttf.
- Pesquise Abril Fatface e clique em Download family.
- Abra a pasta Abril_Fatface. Selecione AbrilFatface-Regular.ttf e arraste para o diretório de recursos de fontes.
- Na pasta de fontes, renomeie Abril_Fatface_Regular.ttf como abril_fatface_regular.ttf.
O diretório de recursos de fontes do projeto vai ficar assim com os três arquivos de fontes personalizadas:
Inicializar fontes
- Na janela do projeto, abra ui.theme > Type.kt. Inicialize as fontes transferidas por download abaixo das instruções de importação e acima de
val
Typography
. Primeiro, inicialize Abril Fatface, definindo a fonte comoFontFamily
e transmitindoFont
com o arquivo de fonteabril_fatface_regular
.
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import com.example.woof.R
val AbrilFatface = FontFamily(
Font(R.font.abril_fatface_regular)
)
- Inicialize Montserrat, abaixo de Abril Fatface, definindo a fonte como
FontFamily
e transmitindoFont
com o arquivo de fontemontserrat_regular
. Paramontserrat_bold
, inclua tambémFontWeight.Bold
. Mesmo que você transmita a versão em negrito do arquivo de fonte, o Compose não vai saber que o arquivo está em negrito. Portanto, faça a vinculação explícita deFontWeight.Bold
.
import androidx.compose.ui.text.font.FontWeight
val AbrilFatface = FontFamily(
Font(R.font.abril_fatface_regular)
)
val Montserrat = FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_bold, FontWeight.Bold)
)
Em seguida, defina os diferentes tipos de título como as fontes que você acabou de adicionar. O objeto Typography
tem parâmetros para 13 tipos de fontes diferentes discutidos acima. Defina quantos forem necessários. Neste app, vamos definir displayLarge
, displayMedium
e bodyLarge
. Na próxima parte deste app, você vai usar o labelSmall
, então ele será adicionado agora.
Confira abaixo uma tabela que mostra a fonte, a espessura e o tamanho de cada título que você está adicionando.
- Defina o atributo
displayLarge
da mesma forma queTextStyle
e preenchafontFamily
,fontWeight
efontSize
com as informações da tabela acima. Isso significa que todo o texto definido comodisplayLarge
terá Abril Fatface como a fonte definida, com uma espessura de fonte normal e umfontSize
de36.sp
.
Repita esse processo para displayMedium
, labelSmall
e bodyLarge
.
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
val Typography = Typography(
displayLarge = TextStyle(
fontFamily = AbrilFatface,
fontWeight = FontWeight.Normal,
fontSize = 36.sp
),
displayMedium = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
labelSmall = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
),
bodyLarge = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
)
)
Se você acessar o arquivo Theme.kt no WoofTheme()
e analisar o MaterialTheme()
, o parâmetro typography
será igual ao Typography val
que você acabou de atualizar.
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
Adicionar tipografia ao texto do app
Agora, você vai adicionar os tipos de título a cada instância de texto no app.
- Adicione
displayMedium
como o estilo dodogName
, já que ele é uma informação curta e importante. AdicionebodyLarge
como o estilo dedogAge
, já que essa informação funciona bem com tamanhos de texto menores.
@Composable
fun DogInformation(
@StringRes dogName: Int,
dogAge: Int,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
Text(
text = stringResource(dogName),
style = MaterialTheme.typography.displayMedium,
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.padding_small))
)
Text(
text = stringResource(R.string.years_old, dogAge),
style = MaterialTheme.typography.bodyLarge
)
}
}
- Agora na
WoofPreview()
, o nome do cachorro mostra a fonte Montserrat em negrito em20.sp
, e a idade do cachorro mostra a fonte Montserrat normal em14.sp
.
Confira abaixo os itens da lista antes e depois de adicionar a tipografia. Observe a diferença entre as fontes do nome e da idade dos cachorros.
Sem tipografia | Com tipografia |
7. Adicionar uma barra na parte de cima
Um Scaffold
é um layout que fornece slots para vários componentes e elementos de tela, como Image
, Row
ou Column
. Um Scaffold
também fornece um slot para uma TopAppBar
, que vamos usar nesta seção.
Uma TopAppBar
pode ser usada para muitos objetivos, mas neste caso você vai fazer o branding e dar uma personalidade ao app. Há quatro tipos diferentes de TopAppBar
: centralizado, pequeno, médio e grande. Neste codelab, você vai implementar uma barra de apps superior central. Você vai criar um elemento combinável semelhante à captura de tela abaixo na seção topBar
de um Scaffold
.
Para esse app, a barra de cima consiste em uma Row
com um logotipo e o texto do título do app. O logotipo mostra uma pata em gradiente e o título do app.
Adicionar imagem e texto à barra de cima
- Em MainActivity.kt, crie um elemento combinável chamado
WoofTopAppBar()
com ummodifier
opcional.
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
}
Scaffold
oferece suporte ao parâmetrocontentWindowInsets
, que pode ajudar a especificar encartes para o conteúdo de scaffold.WindowInsets
são as partes da tela em que o app pode cruzar com a interface do sistema. Elas são transmitidas para o slot de conteúdo usando os parâmetrosPaddingValues
. Clique neste link para mais informações.
O valor contentWindowInsets
é transmitido para a LazyColumn
como o contentPadding
.
@Composable
fun WoofApp() {
Scaffold { it ->
LazyColumn(contentPadding = it) {
items(dogs) {
DogItem(
dog = it,
modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
)
}
}
}
}
- No
Scaffold
, adicione um atributotopBar
e o defina comoWoofTopAppBar()
.
Scaffold(
topBar = {
WoofTopAppBar()
}
)
Confira abaixo a aparência do elemento combinável WoofApp()
:
@Composable
fun WoofApp() {
Scaffold(
topBar = {
WoofTopAppBar()
}
) { it ->
LazyColumn(contentPadding = it) {
items(dogs) {
DogItem(
dog = it,
modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
)
}
}
}
}
Nada mudou em WoofPreview()
porque não há nada na WoofTopAppBar()
. Vamos mudar isso.
- Na
WoofTopAppBar() Composable
, adicione umaCenterAlignedTopAppBar()
e defina o parâmetro do modificador para aWoofTopAppBar()
transmitida.
import androidx.compose.material3.CenterAlignedTopAppBar
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
CenterAlignedTopAppBar(
modifier = modifier
)
}
- Para o parâmetro de título, transmita uma
Row
que vai conter aImage
e oText
daCenterAlignedTopAppBar
.
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier){
CenterAlignedTopAppBar(
title = {
Row() {
}
},
modifier = modifier
)
}
- Adicione o logotipo
Image
àRow
.
- Defina o tamanho da imagem no
modifier
comoimage_size
no arquivodimens.xml
e o padding comopadding_small
no arquivodimens.xml
. - Use
painter
para definir aImage
comoic_woof_logo
a partir da pasta drawable. - Defina
contentDescription
como null. Neste caso, o logotipo do app não adiciona informações semânticas para usuários com problemas de visão. Portanto, não precisamos adicionar uma descrição de conteúdo.
Row() {
Image(
modifier = Modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small)),
painter = painterResource(R.drawable.ic_woof_logo),
contentDescription = null
)
}
- Em seguida, adicione um elemento combinável
Text
dentro daRow
após aImage.
.
- Use
stringResource()
para defini-lo como o valor deapp_name
. Isso define o texto como o nome do app, que é armazenado emstrings.xml
. - Defina o estilo do texto como
displayLarge
, já que o nome do app é curto e importante.
Text(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.displayLarge
)
É isso que aparece em WoofPreview()
. Parece um pouco fora do padrão porque o ícone e o texto não estão alinhados verticalmente.
- Para corrigir isso, adicione um parâmetro de valor
verticalAlignment
aoRow
e o defina comoAlignment.CenterVertically
.
import androidx.compose.ui.Alignment
Row(
verticalAlignment = Alignment.CenterVertically
)
Parece muito melhor!
Este é o elemento combinável WoofTopAppBar()
completo:
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
CenterAlignedTopAppBar(
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small)),
painter = painterResource(R.drawable.ic_woof_logo),
contentDescription = null
)
Text(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.displayLarge
)
}
},
modifier = modifier
)
}
Execute o app e confira como a TopAppBar
dá um toque final perfeito.
Sem a barra de apps na parte de cima | Com a barra de apps na parte de cima |
Agora, confira o app final com o tema escuro.
Parabéns, você chegou ao fim do codelab.
8. Acessar o código da solução
Para baixar o código do codelab concluído, use estes comandos git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout material
Se preferir, você pode baixar o repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Se você quiser conferir o código da solução, acesse o GitHub (em inglês).
9. Conclusão
Você acabou de criar seu primeiro app do Material Design. Você criou uma paleta de cores personalizada para os temas claro e escuro, criou formas para diferentes componentes, fez o download de fontes e as adicionou ao app, além de ter criado uma ótima barra na parte de cima para finalizar o projeto. Aplique as habilidades aprendidas neste codelab e mude cores, formas e tipografias para deixar os apps do seu jeito.
Resumo
- Com os Temas do Material Design (link em inglês), você pode usar o Material Design no seu app com orientações sobre personalização de cores, tipografia e formas.
- O arquivo Theme.kt é onde o tema é definido por meio de um elemento combinável chamado
[your app name]+Theme()
,WoofTheme()
, no caso do app. Nessa função, o objetoMaterialTheme
definecolor
,typography
,shapes
econtent
do app. - Color.kt é onde você lista as cores usadas no app. Em seguida, em Theme.kt, você atribui as cores em
LightColorPalette
eDarkColorPalette
a slots específicos. Nem todos os slots precisam ser atribuídos. - O app pode ativar o recurso Forçar modo escuro, que faz com que o sistema implemente um tema escuro para você. No entanto, os usuários vão ter uma experiência melhor se você implementar o tema escuro no app. Assim, você tem controle total sobre o tema do app.
- Shapes.kt é onde você define as formas do app. Existem três tamanhos de formas (pequeno, médio, grande) e você pode especificar como os cantos vão ser arredondados.
- As formas podem ser usadas para chamar atenção, identificar os componentes, comunicar o estado e expressar a marca.
- Type.kt é onde você inicializa as fontes e atribui
fontFamily
,fontWeight
efontSize
para a escala de tipografia do Material Design. - A escala de tipografia do Material Design (link em inglês) inclui um intervalo de estilos contrastantes com suporte às necessidades e aos conteúdos do app. A escala de tipografia é uma combinação de 15 estilos com suporte ao sistema de tipografia.
10. Saiba mais
- Material Design (link em inglês)
- Tipografia
- Forma
- Cor
- Parâmetros modificadores
- Forçar modo escuro
- Padrões de contraste de acessibilidade (link em inglês)
- Clip
- ContentScale
- Cortar
- Card (link em inglês)
- Scaffold