1. Introdução
Neste codelab, você vai aprender a usar as APIs de temas do Jetpack Compose para estilizar seu aplicativo. Vamos aprender a personalizar cores, formas e tipografia para que sejam usadas de forma consistente em todo o app, oferecendo suporte a vários temas, como temas claros e escuros.
O que você vai aprender
Neste codelab, você vai aprender o seguinte:
- Uma introdução ao Material Design e como ele pode ser usado e personalizado de acordo com sua marca.
- Como o Compose implementa o sistema do Material Design.
- Como definir e usar cores, tipografia e formas em todo o app.
- Como definir o estilo dos componentes.
- Como oferecer suporte a temas claros e escuros.
O que você vai criar
Neste codelab, vamos estilizar um app de leitura de notícias. Começamos com um aplicativo sem estilo e usaremos o que aprendemos para aplicar um tema a ele e oferecer suporte a temas escuros.
Antes: app sem estilo | Depois: app com estilo | Depois: tema escuro |
Pré-requisitos
- Experiência com a sintaxe do Kotlin, incluindo lambdas.
- Noções básicas sobre o Compose.
- Conhecimento básico dos layouts do Compose. Por exemplo,
Row
,Column
eModifier
.
2. Etapas da configuração
Nesta etapa, você vai fazer o download do código, que inclui um app simples para leitura de notícias que vamos estilizar.
O que é necessário
Fazer o download do código
Se você tiver o git instalado, basta executar o comando abaixo. Para verificar se o git está instalado, digite git --version
no terminal ou na linha de comando e verifique se ele é executado corretamente.
git clone https://github.com/googlecodelabs/android-compose-codelabs.git cd android-compose-codelabs/ThemingCodelab
Caso você não tenha o git, clique no botão abaixo para fazer o download de todo o código para este codelab:
Abra o projeto no Android Studio, selecione "File > Import Project" e navegue até o diretório ThemingCodelab
.
O projeto contém três pacotes principais:
com.codelab.theming.data
contém classes de modelo e dados de exemplo. Não é necessário editar esse pacote durante o codelab.com.codelab.theming.ui.start
é o ponto de partida do codelab. Você precisa fazer todas as mudanças explicadas no codelab neste pacote.com.codelab.theming.ui.finish
é o estado final do codelab, para referência.
Criar e executar o app
O aplicativo tem duas configurações de execução que refletem os estados inicial e final do codelab. Selecionar qualquer uma delas e pressionar o botão de execução vai implantar o código no dispositivo ou emulador.
O aplicativo também contém Visualizações de layout do Compose. Navegar para Home.kt
, seja no pacote start
ou no finish
, e abrir a visualização de design mostra várias visualizações que permitem iterações rápidas no código da IU:
3. Temas do Material Design
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 nos Temas do Material Design. Assim, eles oferecem uma maneira sistemática de personalizar o Material Design para refletir melhor a marca do seu produto. Um tema do Material Design é composto pelos atributos color, typography e shape (links em inglês). Essa personalização é refletida automaticamente nos componentes usados para criar o app.
Conhecer os temas do Material Design é útil para entender como definir temas em apps do Jetpack Compose. Veja uma breve descrição dos conceitos. Se você já conhece os Temas do Material Design, pode pular para frente.
Cor
O Material Design define várias cores (link em inglês) com nomes semânticos que podem ser usadas em todo o app:
A cor "primary" (primária) é a cor principal da sua marca, e a "secondary" (secundária) é usada para destaques. Você pode fornecer variantes mais escuras ou mais claras para áreas contrastantes. As cores de "background" (plano de fundo) e "surface" (superfície) são usadas para contêineres de componentes que, internamente, ficam em uma "superfície" do aplicativo. O Material Design também define cores como "on", que são usadas no conteúdo em cima de uma das cores nomeadas. Por exemplo, um texto em um contêiner colorido como "surface", precisa ser colorido como "on surface" (na superfície). Os componentes do Material Design estão configurados para usar essas cores do tema. Por exemplo, a cor de um botão de ação flutuante é definida como secondary
, a de Cards é definida como surface
por padrão e assim por diante (links em inglês).
Ao definir cores com nomes, é possível fornecer paletas de cores alternativas, como um tema claro e um escuro:
Isso também incentiva você a definir uma pequena paleta de cores e a usar de forma consistente em todo o app. A ferramenta de cores do Material Design (link em inglês) ajuda a escolher cores e criar uma paleta, além de garantir que as combinações sejam acessíveis.
Tipografia
Da mesma forma, o Material Design define (link em inglês) vários estilos de tipografia com nomes semânticos:
Embora não seja possível variar os estilos de tipografia por tema, o uso de uma escala promove a consistência dentro do aplicativo. O fornecimento de fontes próprias e outros tipos de personalização é refletido nos componentes do Material Design usados no app. Por exemplo: as barras de apps usam o estilo h6
por padrão, e os botões usam button
. A ferramenta geradora de escala tipográfica (links em inglês) do Material Design pode ajudar você a criar sua própria escala.
Forma
O Material Design oferece suporte sistemático ao uso de formas (link em inglês) que representam sua marca. Ele define três categorias: componentes pequenos, médios e grandes. Cada uma delas pode definir uma forma a ser usada, personalizando o estilo do canto (cortado ou arredondado) e o tamanho.
A personalização do tema das formas vai ser refletida em vários componentes. Por exemplo, os botões e os campos de texto usam o tema pequeno, os cards e as caixas de diálogo usam o médio e as planilhas usam o tema de formato grande por padrão. Confira um mapeamento completo dos componentes para definir os temas neste link. A ferramenta de personalização de formas do Material Design pode ajudar você a gerar um tema para as formas (links em inglês).
Referência
O Material Design tem como padrão um tema de referência, que usa o esquema de cores roxo, a escala tipográfica Roboto e formas ligeiramente arredondadas, como visto nas imagens acima. Se você não especificar ou personalizar o tema, os componentes vão usar o tema de referência.
4. Definir o tema
MaterialTheme
O principal elemento para implementar temas no Jetpack Compose é o elemento MaterialTheme
que pode ser composto. Colocar esse elemento na hierarquia do Compose permite que você especifique personalizações de cor, tipografia e de formas para todos os componentes. Veja como esse elemento é definido na biblioteca:
@Composable
fun MaterialTheme(
colors: Colors,
typography: Typography,
shapes: Shapes,
content: @Composable () -> Unit
) { ...
Depois, é possível acessar os parâmetros transmitidos a esse elemento de composição usando o MaterialTheme
object
, que expõe as propriedades colors
, typography
e shapes
. Vamos ver detalhes sobre cada um deles mais tarde.
Abra Home.kt
e localize a função de composição Home
. Esse é o ponto de entrada principal do app. Enquanto declaramos um MaterialTheme
, não especificamos nenhum parâmetro, portanto, recebemos o estilo de referência padrão:
@Composable
fun Home() {
...
MaterialTheme {
Scaffold(...
Vamos criar parâmetros de cor, tipografia e forma para implementar um tema no app.
Criar um tema
Para centralizar o estilo, recomendamos que você crie seu próprio elemento de composição para encapsular e configurar um MaterialTheme
. Dessa forma, há apenas um local para especificar as personalizações do tema, e você pode reutilizar esse tema facilmente em muitos lugares, como em várias telas ou @Preview
s. Se necessário, é possível criar vários elementos que podem ser compostos para o tema. Por exemplo, caso você queira oferecer suporte a diferentes estilos em diferentes seções do app.
No pacote com.codelab.theming.ui.start.theme
, crie um novo arquivo Kotlin com o nome Theme.kt
. Adicione uma nova função combinável com o nome JetnewsTheme
, que aceita como conteúdo outros elementos combináveis e encapsula um MaterialTheme
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(content = content)
}
Agora, volte para Home.kt
, substitua o MaterialTheme
pelo JetnewsTheme
e o importe:
- MaterialTheme {
+ JetnewsTheme {
...
Ainda não é possível ver as mudanças na @Preview
nesta tela. Atualize a PostItemPreview
e a FeaturedPostPreview
para unir o conteúdo com o novo elemento combinável JetnewsTheme
, para que as visualizações usem o novo tema:
@Preview("Featured Post")
@Composable
private fun FeaturedPostPreview() {
val post = remember { PostRepo.getFeaturedPost() }
+ JetnewsTheme {
FeaturedPost(post = post)
+ }
}
Cores
Esta é a paleta de cores que queremos implementar no app. Por enquanto, há uma paleta apenas para o tema claro, vamos oferecer suporte ao tema escuro em breve:
As cores no Compose são definidas usando a classe Color
. Há vários construtores que permitem especificar a cor como um ULong
ou por um canal de cores separado.
Crie um novo arquivo Color.kt
no pacote theme
. Adicione as cores abaixo como propriedades públicas de alto nível neste arquivo:
val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)
Agora que definimos as cores do nosso app, vamos reuni-las em um objeto Colors
exigido pelo MaterialTheme
, atribuindo cores específicas às cores nomeadas do Material Design. Volte para Theme.kt
e adicione o código abaixo:
private val LightColors = lightColors(
primary = Red700,
primaryVariant = Red900,
onPrimary = Color.White,
secondary = Red700,
secondaryVariant = Red900,
onSecondary = Color.White,
error = Red800
)
Aqui, usamos a função lightColors
para criar a classe Colors
. Ela fornece padrões sensíveis para que não precisemos especificar todas as cores que compõem uma paleta do Material Design. Por exemplo, observe que não especificamos uma cor background
ou muitas das cores "on" já que vamos usar as cores padrão.
Agora, vamos usar essas cores no nosso app. Atualize o elemento de composição JetnewsTheme
para usar nossas novas Colors
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
+ colors = LightColors,
content = content
)
}
Abra Home.kt
e atualize a visualização. Observe o novo esquema de cores refletido em componentes como a TopAppBar
.
Tipografia
Confira a escala tipográfica que queremos implementar no nosso app:
No Compose, podemos definir objetos TextStyle
para estabelecer as informações necessárias para estilizar algum texto. Um exemplo dos atributos dele:
data class TextStyle(
val color: Color = Color.Unset,
val fontSize: TextUnit = TextUnit.Inherit,
val fontWeight: FontWeight? = null,
val fontStyle: FontStyle? = null,
val fontFamily: FontFamily? = null,
val letterSpacing: TextUnit = TextUnit.Inherit,
val background: Color = Color.Unset,
val textAlign: TextAlign? = null,
val textDirection: TextDirection? = null,
val lineHeight: TextUnit = TextUnit.Inherit,
...
)
A escala tipográfica que queremos usa Montserrat para títulos e Domine para o corpo do texto. Os arquivos de fontes relevantes já foram adicionados à pasta res/fonts
do projeto.
Crie um novo arquivo com o nome Typography.kt
no pacote theme
. Primeiro, vamos definir as FontFamily
s, que combinam os diferentes pesos de cada Font
:
private val Montserrat = FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_medium, FontWeight.W500),
Font(R.font.montserrat_semibold, FontWeight.W600)
)
private val Domine = FontFamily(
Font(R.font.domine_regular),
Font(R.font.domine_bold, FontWeight.Bold)
)
Agora, crie um objeto Typography
que um MaterialTheme
aceite, especificando os TextStyle
s para cada estilo semântico na escala:
val JetnewsTypography = Typography(
h4 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 30.sp
),
h5 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 24.sp
),
h6 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 20.sp
),
subtitle1 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
body1 = TextStyle(
fontFamily = Domine,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
body2 = TextStyle(
fontFamily = Montserrat,
fontSize = 14.sp
),
button = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
),
overline = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 12.sp
)
)
Abra o Theme.kt
e atualize o elemento que pode ser composto JetnewsTheme
para usar nossa nova Typography
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
+ typography = JetnewsTypography,
content = content
)
}
Abra Home.kt
e atualize a visualização para ver a nova tipografia.
Formas
Queremos usar formas para expressar nossa marca no app e usar o formato de canto cortado em vários elementos:
O Compose oferece as classes RoundedCornerShape
e CutCornerShape
, que podem ser usadas para definir o tema de formas.
Crie um novo arquivo Shape.kt
no pacote theme
e adicione o código abaixo:
val JetnewsShapes = Shapes(
small = CutCornerShape(topStart = 8.dp),
medium = CutCornerShape(topStart = 24.dp),
large = RoundedCornerShape(8.dp)
)
Abra o Theme.kt
e atualize o elemento que pode ser composto JetnewsTheme
para usar essas Shapes
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
typography = JetnewsTypography,
+ shapes = JetnewsShapes,
content = content
)
}
Abra o Home.kt
e atualize a visualização para ver como o Card
que mostra a postagem em destaque reflete o tema de formas recém-aplicado.
Tema escuro
Oferecer suporte a um tema escuro no app não só ajuda o app a se integrar melhor aos dispositivos dos usuários, que têm um tema escuro global no Android 10 e versões mais recentes, mas também pode reduzir o uso de energia e oferecer suporte às necessidades de acessibilidade. O Material Design oferece uma orientação de design (em inglês) sobre como criar um tema escuro. Confira uma paleta de cores alternativa que gostaríamos de implementar para o tema escuro:
Abra Color.kt
e adicione as cores abaixo:
val Red200 = Color(0xfff297a2)
val Red300 = Color(0xffea6d7e)
Agora, abra Theme.kt
e adicione:
private val DarkColors = darkColors(
primary = Red300,
primaryVariant = Red700,
onPrimary = Color.Black,
secondary = Red300,
onSecondary = Color.Black,
error = Red200
)
Depois, atualize o JetnewsTheme
:
@Composable
fun JetnewsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
+ colors = if (darkTheme) DarkColors else LightColors,
typography = JetnewsTypography,
shapes = JetnewsShapes,
content = content
)
}
Aqui, adicionamos um novo parâmetro para usar um tema escuro e o padronizamos para consultar a configuração global do dispositivo. Esse é um bom padrão, mas ainda é fácil de substituir se você quiser que uma determinada tela sempre/nunca seja escura ou criar uma @Preview
com tema escuro.
Abra o Home.kt
e crie uma nova visualização do elemento combinável FeaturedPost
para mostrar o tema escuro:
@Preview("Featured Post • Dark")
@Composable
private fun FeaturedPostDarkPreview() {
val post = remember { PostRepo.getFeaturedPost() }
JetnewsTheme(darkTheme = true) {
FeaturedPost(post = post)
}
}
Atualize o painel de visualização para ver o tema escuro.
5. Como trabalhar com cores
Na última etapa, vimos como criar um tema próprio para definir cores, estilos de tipo e formas no app. Todos os componentes do Material Design usam essas personalizações por padrão. Por exemplo, o elemento FloatingActionButton
que pode ser composto usa a cor secondary
do tema por padrão, mas é possível definir uma cor alternativa especificando um valor diferente para esse parâmetro:
@Composable
fun FloatingActionButton(
backgroundColor: Color = MaterialTheme.colors.secondary,
...
) {
Você nem sempre vai querer usar as configurações padrão. Esta seção mostra como trabalhar com cores no app.
Cores brutas
Como vimos anteriormente, o Compose oferece uma classe Color
. É possível criar as cores localmente, armazená-las em um object
etc.:
Surface(color = Color.LightGray) {
Text(
text = "Hard coded colors don't respond to theme changes :(",
textColor = Color(0xffff00ff)
)
}
Color
tem vários métodos úteis, por exemplo, copy
, que permite criar uma nova cor com diferentes valores alfa/vermelho/verde/azul.
Cores do tema
Uma abordagem mais flexível é aproveitar cores do seu tema:
Surface(color = MaterialTheme.colors.primary)
Neste caso, estamos usando o object
do MaterialTheme
em que a propriedade colors
retorna as Colors
definidas no elemento combinável MaterialTheme
. Isso significa que podemos oferecer suporte a diferentes aparências apenas fornecendo conjuntos de cores diferentes para o nosso tema, sem precisar mexer no código do aplicativo. Por exemplo, nossa AppBar
usa a cor primary
, e o plano de fundo da tela usa surface
. A mudança de cores do tema é refletida nesses elementos de composição:
Como cada cor no nosso tema é uma instância de Color
, também podemos derivar cores facilmente usando o método copy
:
val derivedColor = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)
No exemplo acima, fizemos uma cópia da cor onSurface
, mas com 10% de opacidade. Essa abordagem garante que as cores funcionem com temas diferentes, em vez de precisar codificar cores estáticas.
Cores de superfície e conteúdo
Muitos componentes aceitam um par de cores e de "cores de conteúdo":
Surface(
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
...
TopAppBar(
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
...
Isso permite não apenas definir a cor de um elemento de composição, mas também fornecer uma cor padrão para o "conteúdo", ou seja, para elementos de composição contidos nele. Muitos elementos que podem ser compostos usam essa cor para o conteúdo por padrão, como a cor do Text
ou a tonalidade do Icon
. O método contentColorFor
extrai a cor "on" adequada para todas as cores do tema. Por exemplo, se você definir um plano de fundo primary
, ele vai retornar onPrimary
como a cor do conteúdo. Se você definir uma cor que não seja do tema para o plano de fundo, vai precisar fornecer uma cor para o conteúdo adequada.
Surface(color = MaterialTheme.colors.primary) {
Text(...) // default text color is 'onPrimary'
}
Surface(color = MaterialTheme.colors.error) {
Icon(...) // default tint is 'onError'
}
Você pode usar LocalContentColor
e CompositionLocal
para extrair a cor que contrasta com o plano de fundo atual:
BottomNavigationItem(
unselectedContentColor = LocalContentColor.current ...
Ao definir a cor de qualquer elemento, prefira usar uma Surface
para isso, porque ela define uma cor para o conteúdo com um valor CompositionLocal
adequado. Cuidado com chamadas diretas de Modifier.background
que não definem uma cor adequada.
-Row(Modifier.background(MaterialTheme.colors.primary)) {
+Surface(color = MaterialTheme.colors.primary) {
+ Row(
...
No momento, os componentes Header
sempre têm um plano de fundo Color.LightGray
. Isso pode parecer normal em um tema claro, mas tem alto contraste em relação ao plano de fundo no tema escuro. Eles também não definem a cor específica do texto, mas herdam a cor atual do conteúdo, que pode não ter um bom contraste em relação ao plano de fundo:
Vamos corrigir isso. No elemento que pode ser composto Header
em Home.kt
, remova o modificador background
especificando a cor codificada. Em vez disso, envolva o Text
em uma Surface
com uma cor derivada do tema e especifique que o conteúdo precisa ter a cor primary
:
+ Surface(
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+ contentColor = MaterialTheme.colors.primary,
+ modifier = modifier
+ ) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
- .background(Color.LightGray)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
+ }
Alfa do conteúdo
Com frequência, queremos enfatizar ou não o conteúdo para comunicar a importância e fornecer uma hierarquia visual. O Material Design recomenda (link em inglês) o uso de diferentes níveis de opacidade para transmitir esses níveis de importância variados.
Isso é implementado pelo Jetpack Compose via LocalContentAlpha
. É possível especificar um Alfa de conteúdo para uma hierarquia fornecendo um valor para esse CompositionLocal
. Os elementos filhos que podem ser compostos podem usar esse valor. Por exemplo: Text
e Icon
, por padrão, usam a combinação de LocalContentColor
ajustada para usar LocalContentAlpha
. O Material Design especifica alguns valores Alfa padrão (high
, medium
e disabled
), que são modelados pelo objeto ContentAlpha
. Observe que MaterialTheme
define LocalContentAlpha
como ContentAlpha.high
por padrão.
// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting a different content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(...)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(...)
Text(...)
}
Isso torna mais fácil e consistente representar a importância dos componentes.
Usaremos o Alfa de conteúdo para esclarecer a hierarquia de informações da postagem em destaque. Em Home.kt
, no elemento de composição PostMetadata
, dê ênfase aos metadados medium
:
+ CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = text,
modifier = modifier
)
+ }
Tema escuro
Como vimos anteriormente, para implementar temas escuros no Compose, basta fornecer diferentes conjuntos de cores e consultas no tema. Algumas exceções são:
É possível confirmar se você está executando um tema claro:
val isLightTheme = MaterialTheme.colors.isLight
Esse valor é definido pelas funções do builder lightColors e darkColors.
Nos modos escuros do Material Design, as superfícies com elevações mais altas recebem sobreposições de elevação (link em inglês) e o plano de fundo é clareado. Isso é implementado automaticamente ao usar uma paleta de cores escuras:
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
...
Podemos notar esse comportamento automático no nosso app nos componentes TopAppBar
e Card
que estamos usando. Por padrão, eles têm elevações de 4 dp e 1 dp. O plano de fundo é clareado automaticamente no tema escuro para representar melhor essa elevação:
O Material Design sugere evitar ter áreas grandes com cores brilhantes no tema escuro. Um padrão comum é colorir um contêiner com a cor primary
em temas claros e surface
em temas escuros. Muitos componentes usam essa estratégia por padrão, por exemplo, as barras de apps e a navegação na parte de baixo da tela (links em inglês). Para facilitar a implementação, a classe Colors
oferece uma cor da primarySurface
que fornece exatamente esse comportamento, e esses componentes são usados por padrão.
No momento, o app define a cor da barra de apps como primary
. Podemos seguir essa orientação mudando a cor para primarySurface
ou removendo esse parâmetro já que ele é o padrão. Na AppBar
combinável, mude o parâmetro backgroundColor
da TopAppBar
:
@Composable
private fun AppBar() {
TopAppBar(
...
- backgroundColor = MaterialTheme.colors.primary
+ backgroundColor = MaterialTheme.colors.primarySurface
)
}
6. Como trabalhar com texto
Ao trabalhar com texto, usamos o elemento combinável Text
para mostrar texto, o TextField
e o OutlinedTextField
para entradas de texto e o TextStyle
para aplicar um único estilo ao texto. Podemos usar as AnnotatedString
s para definir vários estilos a textos.
Como vimos no caso das cores, os componentes do Material Design que exibem texto vão usar nossas personalizações de tipografia no tema:
Button(...) {
Text("This text will use MaterialTheme.typography.button style by default")
}
Conseguir isso é um pouco mais complexo do que usar parâmetros padrão, como vimos no caso das cores. Isso ocorre porque os componentes não costumam mostrar texto, mas oferecem "APIs de slot", o que possibilita transmitir um elemento combinável Text
. Então, como os componentes definem um estilo de tipografia no tema? Internamente, eles usam o elemento ProvideTextStyle
que pode ser composto (que usa um CompositionLocal
) para definir um TextStyle
"atual". O elemento de composição Text
volta para o padrão de consultar o estilo "atual" se você não fornecer um parâmetro textStyle
concreto.
Veja como exemplo as classes Button
e Text
do Compose:
@Composable
fun Button(
// many other parameters
content: @Composable RowScope.() -> Unit
) {
...
ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
...
content()
}
}
@Composable
fun Text(
// many, many parameters
style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...
Estilos de texto do tema
Assim como com as cores, é melhor extrair os TextStyle
s do tema atual, o que incentiva você a usar um conjunto pequeno e consistente de estilos e os torna mais fáceis de manter. A MaterialTheme.typography
extrai a instância Typography
definida no elemento MaterialTheme
, permitindo que você use os estilos definidos:
Text(
style = MaterialTheme.typography.subtitle2
)
Caso você precise personalizar um TextStyle
, é possível copy
(copiar) e substituir as propriedades (que são apenas uma data class
) ou usar o elemento combinável Text
e que aceita diversos parâmetros de estilo que vão sobrepor qualquer TextStyle
:
Text(
text = "Hello World",
style = MaterialTheme.typography.body1.copy(
background = MaterialTheme.colors.secondary
)
)
Text(
text = "Hello World",
style = MaterialTheme.typography.subtitle2,
fontSize = 22.sp // explicit size overrides the size in the style
)
Muitos lugares no app aplicam automaticamente os TextStyle
s do tema. Por exemplo, os estilos da TopAppBar
usam o title
como h6
e o ListItem
define o estilo do texto principal e secundário como subtitle1
e body2
, respectivamente.
Vamos aplicar os estilos tipográficos do tema ao restante do app. Configure o Header
para usar subtitle2
e o texto da FeaturedPost
para usar h6
como o título e body2
para o autor e os metadados:
@Composable
fun Header(...) {
...
Text(
text = text,
+ style = MaterialTheme.typography.subtitle2
Vários estilos
Se você precisar aplicar vários estilos a um texto, use a classe AnnotatedString
para aplicar a marcação, adicionando SpanStyle
s aos intervalos no texto. Você pode adicionar isso dinamicamente ou usar a sintaxe DSL para criar conteúdo:
val text = buildAnnotatedString {
append("This is some unstyled text\n")
withStyle(SpanStyle(color = Color.Red)) {
append("Red text\n")
}
withStyle(SpanStyle(fontSize = 24.sp)) {
append("Large text")
}
}
Vamos estilizar as tags que descrevem cada postagem no nosso app. Atualmente, elas usam o mesmo estilo de texto que o restante dos metadados. Usaremos o estilo de texto overline
e uma cor de plano de fundo para diferenciar os dois. No elemento de composição PostMetadata
:
+ val tagStyle = MaterialTheme.typography.overline.toSpanStyle().copy(
+ background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
+ )
post.tags.forEachIndexed { index, tag ->
...
+ withStyle(tagStyle) {
append(" ${tag.toUpperCase()} ")
+ }
}
7. Como trabalhar com formas
Assim como a cor e a tipografia, a definição do tema da forma vai ser refletida nos componentes do Material Design. Por exemplo, os Button
s vão usar a forma definida para componentes pequenos:
@Composable
fun Button( ...
shape: Shape = MaterialTheme.shapes.small
) {
Assim como as cores, os componentes do Material Design usam parâmetros padrão para que seja simples confirmar qual categoria de forma um componente vai usar ou para fornecer uma alternativa. Para mapear totalmente os componentes para a categoria de forma, consulte a documentação (em inglês).
Alguns componentes usam formas do tema modificadas de acordo com o contexto. Um exemplo é o TextField
, que usa por padrão o tema de forma pequena, mas aplica um tamanho de canto zero aos cantos de baixo:
@Composable
fun FilledTextField(
// other parameters
shape: Shape = MaterialTheme.shapes.small.copy(
bottomStart = ZeroCornerSize, // overrides small theme style
bottomEnd = ZeroCornerSize // overrides small theme style
)
) {
Formas do tema
É possível usar formas ao criar seus próprios componentes com elementos que podem ser compostos ou Modifier
s que aceitam formas. Por exemplo, Surface
, Modifier.clip
, Modifier.background
, Modifier.border
e assim por diante.
@Composable
fun UserProfile(
...
shape: Shape = MaterialTheme.shapes.medium
) {
Surface(shape = shape) {
...
}
}
Vamos adicionar formas às imagens mostradas no PostItem
e aplicar a forma small
do tema com um clip
Modifier
para cortar o canto superior esquerdo:
@Composable
fun PostItem(...) {
...
Image(
painter = painterResource(post.imageThumbId),
+ modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
)
8. "Estilos" de componentes
O Compose não oferece uma maneira explícita de extrair o estilo de um componente, como os estilos CSS ou da visualização do Android. Como todos os componentes do Compose são criados no Kotlin, há outras maneiras de atingir o mesmo objetivo. Em vez disso, crie sua própria biblioteca de componentes personalizados para usar em todo o app.
Já fizemos isso no nosso app:
@Composable
fun Header(
text: String,
modifier: Modifier = Modifier
) {
Surface(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
contentColor = MaterialTheme.colors.primary,
modifier = modifier.semantics { heading() }
) {
Text(
text = text,
style = MaterialTheme.typography.subtitle2,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
O elemento Header
é essencialmente um Text
estilizado que pode ser usado em todo o app.
Todos os componentes são feitos de elementos de níveis mais baixos. Use esses mesmos elementos para personalizar os componentes do Material Design. Por exemplo, vimos que o Button
usa o elemento ProvideTextStyle
para definir um estilo de texto padrão para o conteúdo transmitido a ele. Você pode usar exatamente o mesmo mecanismo para definir seu próprio estilo de texto:
@Composable
fun LoginButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonConstants.defaultButtonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier
) {
ProvideTextStyle(...) { // set our own text style
content()
}
}
}
Neste exemplo, criamos nosso próprio "estilo" do LoginButton
ao combinar a classe Button
padrão e especificar certas propriedades, como uma backgroundColor
e estilo de texto diferentes.
Também não há um conceito de estilo padrão, ou seja, uma maneira padrão de personalizar a aparência de um tipo de componente. Mais uma vez, é possível fazer isso criando seu próprio componente que envolve e personaliza um componente da biblioteca. Digamos que você queira personalizar a forma de todos os Button
s do app, mas não queira mudar o tema de formato pequeno, o que afetaria outros componentes que não são do Button
. Para fazer isso, crie seu próprio elemento que pode ser composto e use-o em todo o app:
@Composable
fun AcmeButton(
// expose Button params consumers should be able to change
) {
val acmeButtonShape: Shape = ...
Button(
shape = acmeButtonShape,
// other params
)
}
9. Parabéns
Parabéns! Você concluiu este codelab e estilizou um app do Jetpack Compose.
Você implementou um tema do Material Design, personalizando a cor, a tipografia e as formas usadas em todo o app para expressar sua marca e promover consistência. Você adicionou suporte a temas claros e escuros.
Qual é a próxima etapa?
Confira os outros codelabs no programa de aprendizagem do Compose:
Leia mais
- Guia de temas do Compose
- Temas do Material Design (link em inglês)
Apps de exemplo
- Owl demonstrando vários temas
- Jetcaster demonstrando temas dinâmicos
- Jetsnack demonstrando a implementação de um sistema de design personalizado