
Todo desenvolvedor Android precisava trabalhar com estilos de qualquer maneira. Alguém se sente confiante com eles, alguém tem apenas conhecimento superficial, o que geralmente não lhes permite resolver o problema por conta própria.
Em antecipação ao lançamento do tema sombrio, foi decidido atualizar na memória todas as informações relacionadas a temas e estilos nos aplicativos Android.
O que será discutido:
- Considere os conceitos básicos de temas e estilos nos aplicativos Android, veja quais oportunidades eles nos oferecem;
- Vamos criar um tema simples usando Componentes de materiais e brincar com a redefinição de estilos;
- Vamos ver como o tema sombrio funciona;
- Formule recomendações para trabalhar com estilos.
Vamos começar com o básico
Em sua estrutura, temas e estilos têm uma estrutura comum:
<style name="MyStyleOrTheme"> <item name="key">value</item> </style>
Para criar, use a tag style
. Cada estilo tem um nome e armazena os parâmetros de key-value
.
Tudo é bem simples. Mas qual é a diferença entre tema e estilo?
A única diferença é como os usamos.
Theme
Um tema é um conjunto de parâmetros que se aplicam a todo o aplicativo, Atividade ou Exibir componente. Ele contém as cores básicas do aplicativo, estilos para renderizar todos os componentes do aplicativo e várias configurações.
Tópico de exemplo:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorOnPrimary">@color/color_on_primary</item> <item name="colorOnError">@color/color_on_error</item> <item name="textAppearanceHeadline1">@style/TextAppearance.MyTheme.Headline1</item> <item name="bottomSheetDialogTheme">@style/ThemeOverlay.MyTheme.BottomSheetDialog</item> <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item> <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.FilledBox</item> <item name="android:windowTranslucentStatus">true</item> </style>
O tema redefiniu as principais cores do aplicativo ( colorPrimary
, colorSecondary
), o estilo do texto ( textAppearanceHeadline1
) e alguns componentes padrão do aplicativo, além da opção de uma barra de status transparente.
Para que o estilo se torne um tópico real, é necessário herdar (falaremos sobre herança um pouco mais tarde) da implementação padrão do tópico.
Estilo
Um estilo é um conjunto de parâmetros para denominar um único componente de Visualização.
Estilo de exemplo para TextInputLayout :
<style name="Widget.MyApp.CustomTextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="boxBackgroundMode">outline</item> <item name="boxStrokeColor">@color/color_primary</item> <item name="shapeAppearanceOverlay">@style/MyShapeAppearanceOverlay</item> </style>
Atributo
Um atributo é uma chave de estilo ou tema. Estes são pequenos tijolos a partir dos quais tudo é construído:
colorPrimary colorSecondary colorOnError boxBackgroundMod boxStrokeColor shapeAppearanceOverlay ...
Todas essas chaves são atributos padrão.
Podemos criar nossos próprios atributos:
<attr name="myFavoriteColor" format="color|reference" />
O atributo myFavoriteColor
apontará para uma cor ou vinculará a um recurso de cores.
No formato, podemos especificar valores bastante padrão:
- cor
- referência
- corda
- enum
- fração
- dimensão
- booleano
- bandeiras
- flutuar
- inteiro
Por sua natureza, um atributo é uma interface . Ele deve ser implementado no tópico:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="myFavoriteColor">@color/color_favorite</item> </style>
Agora podemos nos referir a isso. A estrutura geral do recurso é assim:

1 — , ; 2 — namespace ( Material Components Library); 3 — , (); 4 — .
Bem, finalmente, vamos mudar, por exemplo, a cor do texto do campo:
<androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/myFavoriteColor"/>
Graças aos atributos, podemos adicionar qualquer tipo de abstração que mudará dentro do tópico.
Herança de temas e estilos
Como no OOP, podemos adotar a funcionalidade de uma implementação existente. Existem duas maneiras de fazer isso:
- Explícito (explícito)
- Implícito (implicitamente)
Para herança explícita, especificamos o pai usando a palavra parent
chave parent
:
<style name="SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
Para herança implícita, usamos dot-notation
para indicar o pai:
<style name="SnackbarStyle.Green"> </style>
Não há diferença no trabalho dessas abordagens.
Muitas vezes, podemos encontrar estilos semelhantes:
<style name="Widget.MyApp.Snackbar" parent="Widget.MaterialComponents.Snackbar"> </style>
Pode parecer que o estilo é criado por herança dupla. Este não é realmente o caso. A herança múltipla é proibida. Nesta definição, a herança explícita sempre vence .
Ou seja, um estilo será criado com o nome Widget.MyApp.Snackbar
, que é o descendente de Widget.MaterialComponents.Snackbar
.
Themeoverlay
ThemeOverlay - esses são temas especiais "leves" que permitem substituir os atributos do tema principal do componente View.
Não iremos longe, por exemplo, mas aceitamos um caso do nosso aplicativo. Os designers decidiram que precisamos criar um campo de login padrão, que terá uma cor diferente do estilo principal.
Com o tópico principal, o campo de entrada fica assim:

Parece ótimo, mas os designers insistem que o campo seja em estilo marrom.
Ok, como podemos resolver esse problema?
A solução correta é usar ThemeOverlay .
Crie ThemeOverlay
e redefina a cor principal do tema :
<style name="ThemeOverlay.MyApp.Login" parent="ThemeOverlay.MaterialComponents.TextInputEditText"> <item name="colorPrimary">@color/colorBrown</item> </style>
Em seguida, nós o especificamos usando a tag android:theme
especial em nosso TextInputLayout
:
<com.google.android.material.textfield.TextInputLayout android:theme="@style/ThemeOverlay.MyApp.Login" android:hint="Login" ... > <com.google.android.material.textfield.TextInputEditText ... /> </com.google.android.material.textfield.TextInputLayout>
Tudo funciona como precisamos.

Obviamente, surge a pergunta - como funciona sob o capô?
Essa mágica permite ContextThemeWrapper
o ContextThemeWrapper
. Ao criar uma View no LayoutInflater
, será criado um contexto em que o tema atual será tomado como base e os parâmetros que especificamos em nosso tema Overlay serão redefinidos nele.
../LayoutInflater.java final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle();
Da mesma forma, podemos substituir independentemente qualquer parâmetro de tema no aplicativo.
A sequência de aplicação de temas e estilos ao componente Visualizar

A principal prioridade é o arquivo de marcação. Se um parâmetro for definido nele, todos os parâmetros semelhantes serão ignorados.
<Button android:textColor="@color/colorRed" ... />
A próxima prioridade é o estilo de exibição:
<Button style=“@Widget.MyApp.ButtonStyle" ... />
A seguir, são usados estilos predefinidos para o componente:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“materialButtonStyle”>@Widget.MyApp.ButtonStyle</item> </style>
Se nenhum parâmetro foi encontrado, os atributos do tema são usados:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“colorPrimary”>@colorPrimary</item> </style>
Em geral, é tudo o que você precisa saber para começar a trabalhar com os tópicos. Agora, vamos dar uma olhada rápida na biblioteca de design atualizada dos Componentes de Material.
Os componentes materiais podem vir conosco
O Material Components foi introduzido no Google I / O 2018 e substitui a Design Support Library.
A biblioteca nos dá a oportunidade de usar componentes atualizados do Material Design 2.0. Além disso, muitas opções interessantes de personalização apareceram nele. Tudo isso permite que você escreva aplicativos brilhantes e exclusivos.


Aqui estão alguns exemplos de aplicativos no novo estilo: Coruja , Resposta , Guindaste .
Vamos seguir praticando
Para criar um tópico, você precisa herdar do tópico base:
Theme.MaterialComponents Theme.MaterialComponents.NoActionBar Theme.MaterialComponents.Light Theme.MaterialComponents.Light.NoActionBar Theme.MaterialComponents.Light.DarkActionBar Theme.MaterialComponents.DayNight Theme.MaterialComponents.DayNight.NoActionBar Theme.MaterialComponents.DayNight.DarkActionBar
Todos eles são muito semelhantes aos temas do AppCompat
, mas possuem atributos e configurações adicionais.
Você pode aprender mais sobre os novos atributos em material.io .
Se, por algum motivo, você não puder mudar para um novo tópico no momento, os temas do Bridge
funcionarão. Eles são AppCompat
temas do AppCompat
e têm todos os novos atributos dos Componentes materiais. Você só precisa adicionar o postfix do Bridge
e usar todos os recursos sem medo:
Theme.MaterialComponents.Light.Bridge
E aqui está o nosso tópico:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorSecondaryVariant">@color/color_secondary_variant</item> <style>
Os nomes das cores primárias (cores da marca) foram alterados:
colorPrimary — ( AppCompat); colorPrimaryVariant — ( colorPrimaryDark AppCompat); colorSecondary — ( colorAccent AppCompat); colorSecondaryVariant — .
Mais informações sobre cores podem ser encontradas em material.io .
Eu já mencionei que o tema contém estilos padrão para cada componente do View. Por exemplo, para Snackbar
estilo será chamado snackbarStyle
, para checkbox
- checkboxStyle
e, em seguida, tudo será semelhante. Um exemplo colocará tudo em seu lugar:

Crie seu próprio estilo e aplique-o ao tema:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="snackbarStyle">@style/Widget.MyApp.SnackbarStyle</item> </style> <style name="Widget.MyApp.SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
É importante entender que, quando você redefine um estilo em um tema, ele se aplica a todas as Visualizações desse tipo no aplicativo (Atividade).
Se você deseja aplicar o estilo a apenas uma Visualização específica, use a marca de style
no arquivo com marcação:
<com.google.android.material.button.MaterialButton style="@style/Widget.MyApp.SnackbarStyle" ... />
Uma das inovações que realmente me impressionou foi o ShapeAppearance . Permite alterar a forma dos componentes diretamente no assunto!
Cada componente View pertence a um determinado grupo:
shapeAppearance Small Component
shapeAppearance Medium Component
shapeAppearance Large Component
Como podemos entender pelo nome, em grupos de visualizações de tamanhos diferentes.

Verifique na prática:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="shapeAppearanceSmallComponent">@style/Widget.MyApp.SmallShapeAppearance</item> </style> <style name="Widget.MyApp.SmallShapeAppearance" parent=“ShapeAppearance.MaterialComponents.SmallComponent”> <item name="cornerFamilyTopLeft">rounded</item> <item name="cornerFamilyBottomRight">cut</item> <item name="cornerSizeTopLeft">20dp</item> <item name="cornerSizeBottomRight">15dp</item> </style>
Criamos Widget.MyApp.SmallShapeAppearance
para os componentes "pequenos". Nós arredondamos o canto superior esquerdo em 20dp
e o canto inferior direito foi cortado em 15dp
.
Obteve este resultado:

Parece interessante. Será que vai funcionar na vida real? O tempo dirá.
Assim como nos estilos, só podemos aplicar o ShapeAppearance
a um componente View.
O que há em um tópico obscuro?
O lançamento do Android Q acontecerá muito em breve, e com ele o tema escuro oficial chegará até nós.
Talvez um dos recursos mais interessantes e espetaculares da nova versão do Android seja a aplicação automática de um tema sombrio para todo o aplicativo com uma linha de código.
Parece ótimo, vamos tentar. Sugiro pegar um cliente gitlab favorito do terrakok .
Permitir repintar o aplicativo (desativado por padrão):
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:forceDarkAllowed">true</item> </style>
O android:forceDarkAllowed
está disponível na API 29 (Android Q).
Começamos, veja o que aconteceu:

Concorde que, para uma linha de código, parece muito legal.
Obviamente, existem problemas - o BottomNavigationBar
funde com o plano de fundo, o carregador permanece branco, a seleção do código sofre e, ao que parece, tudo, pelo menos nada sério me ocorreu.
Tenho certeza de que gastar pouco tempo pode resolver os principais problemas. Por exemplo, desativando o modo escuro automático para visualizações individuais (sim, também é possível - android:forceDarkAllowed
está disponível para Visualizar em um arquivo de marcação).
Deve-se lembrar que esse modo está disponível apenas para temas claros; se você usar um escuro, um tema escuro forçado não funcionará.
As recomendações de trabalho podem ser encontradas na documentação e no material.io .
E se queremos fazer tudo por conta própria?
Não importa o quão fácil seja usar um tema escuro forçado, este modo não tem flexibilidade. De fato, tudo funciona de acordo com regras predefinidas que podem não se adequar a nós e, mais importante, ao cliente. Penso que essa decisão pode ser considerada temporária, até que implementemos um tópico obscuro.
Na API 8 (Froyo), o qualificador -night
foi adicionado, que até hoje é usado para aplicar um tema sombrio. Permite aplicar automaticamente o tema desejado, dependendo da hora do dia.
Nos temas do DayNight
, essa implementação já é usada, basta herdarmos deles.
Vamos tentar escrever o nosso:
../values/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents.Light"/> <style name="Theme.MyApp.Main" parent="Theme.DayNight.Base> <!-- ... --> </style> ../values-night/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents"/>
No recurso usual para o tema ( values/themes.xml
) herdamos do tema light, na "noite" ( values-night/themes.xml
) herdamos do tema dark.
Só isso. Temos uma implementação de biblioteca de um tema sombrio. Agora devemos apoiar recursos para dois tópicos.
Para alternar entre temas enquanto o aplicativo está em execução, você pode usar AppCompatDelegate.setDefaultNightMode
, que usa os seguintes parâmetros:
MODE_NIGHT_NO
- tema claro;MODE_NIGHT_YES
- tema escuro;MODE_NIGHT_AUTO_BATTERY
- Modo automático. O tema escuro é ativado se o modo de economia de energia estiver ativo;MODE_NIGHT_FOLLOW_SYSTEM
- Modo baseado nas configurações do sistema.
O que devemos considerar ao trabalhar com temas e estilos?
Como observei, o Google começou oficialmente a forçar um tópico sombrio. Estou certo de que muitos clientes começaram a receber perguntas - "Podemos adicionar um tema sombrio?". É bom que você faça tudo certo desde o início e é fácil alterar as cores claras para escuras enquanto recebe um aplicativo totalmente repintado.
Infelizmente, esse nem sempre é o caso. Existem aplicativos antigos que exigem um esforço considerável para fazer as alterações necessárias.
Vamos tentar formular recomendações para trabalhar com estilos juntos:
1. O seletor de cores
Eu acho que todo desenvolvedor enfrenta uma situação em que uma cor estranha aparece no novo layout que ainda não está definido na paleta de aplicativos. O que fazer neste caso?
A resposta correta é conversar com o designer e tentar desenvolver uma paleta de cores. Agora, existem muitos programas (Zeplin, Sketch etc.) que permitem renderizar cores primárias e reutilizá-las.
Quanto mais cedo você fizer isso, menos dores de cabeça terá no futuro.
2. Soletre cores por seus nomes próprios
Em cada aplicativo, há uma cor que possui muitas opções de brilho. Você pode começar a inventar nomes para eles:
<color name="green_tiny">...</color> <color name="green_light">...</color> <color name="green_dark">...</color>
Concordo, não parece muito bom. Surge imediatamente a pergunta - que cor é mais clara que tiny
ou light
? E se tivermos uma dúzia de opções?
É melhor colorVariant
o conceito do Google e adicionar o brilho apropriado aos nomes das cores (o Google chama essa opção de cor - colorVariant
):
<color name="material_green_300">...</color> <color name="material_green_700">...</color> <color name="material_green_900">...</color>
Com essa abordagem, podemos ter várias opções de brilho de uma cor e não precisamos criar nomes específicos, o que é realmente difícil.
3. Abstrair de uma cor específica se ela mudar em tópicos diferentes
Como estamos escrevendo um aplicativo no qual haverá pelo menos dois tópicos, não podemos nos dar ao luxo de nos referir a uma cor específica se ela for implementada nos temas de maneiras diferentes.
Vejamos um exemplo:

Vemos que, em um tema claro, por exemplo, a barra de ferramentas é colorida em roxo e, no escuro, é cinza escuro. Como implementaríamos esse comportamento usando apenas os recursos dos temas?
Tudo é bem simples - criaremos um atributo e o implementaremos em temas claros e escuros com a cor apropriada, conforme descrito anteriormente.
O Google recomenda associar nomes de atributos à semântica de uso.
4. Não tenha medo de criar arquivos de recursos
Quando vários estilos, temas e atributos diferentes são digitados em styles.xml
, torna-se difícil de manter.
É melhor agrupar tudo em arquivos separados:
themes.xml — Theme & ThemeOverlay styles.xml — Widget styles type.xml — TextAppearance, text size etc shape.xml — ShapeAppearance motion.xml — Animations styles system_ui.xml — Booleans, colors for UI control //may be other files
Uma regra tão simples evitará arquivos de Deus e, portanto, será mais fácil manter estilos.
5. Reutilize ao máximo
O que faremos se quisermos redefinir um atributo que está disponível apenas em uma versão específica da API?
Podemos criar dois tópicos separados:
../values/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> </style> ../values-v27/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> <name="android:windowLightNavigationBar">...</item> </style>
Agora temos um tema com todos os parâmetros para cada versão da API? Claro que não! Criaremos um tópico básico em que os atributos básicos disponíveis para todas as versões da API serão determinados e herdados na versão desejada da API:
../values/themes.xml <style name="Theme.MyApp.Base" parent=”Theme.MaterialComponents.DayNight.NoActionBar”> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base”/> ../values-v27/themes.xml <style name="Theme.MyApp.Base.V27" parent="Theme.MyApp.Base"> <name="android:windowLightNavigationBar">...</item> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base.V27”/>
Por esse princípio, todos os tópicos da biblioteca padrão são criados.
6. Use recursos vetoriais e tonalidade
Eu acho que não vale a pena dizer por que os recursos vetoriais são bons. Todo mundo já sabe (apenas no caso, um link para a documentação ). Bem, tingir nos ajudará a colori-los em cores de tema.
Você pode ver o que é tingimento e como trabalhar com ele neste exemplo .
7.? Android: attr / ... vs? Attr / ...
Ao acessar recursos, temos a oportunidade de usar os atributos do sistema e os atributos da biblioteca Componentes de Materiais. É importante entender que alguns atributos existem apenas com uma versão específica da API. Como todos sabemos bem, acessar um recurso inexistente leva a uma falha (o fiapo, é claro, nos dirá se algo está errado, mas você nem sempre deve confiar nele)
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
No primeiro caso, acessamos o recurso do sistema, conforme indicado pelo android
. No segundo caso, para um atributo da biblioteca em que a compatibilidade com versões anteriores é implementada.
É melhor sempre usar a segunda opção.
8. Sempre especifique pai para estilo
Pode haver parâmetros no estilo pai, sem os quais o componente será renderizado incorretamente; portanto, você sempre deve especificar o pai.
<style name="Widget.MyApp.LoginInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="errorTextColor">@color/colorError</item> </style>
9. Tema, estilo ou ...?
Ao criar seus próprios temas e estilos, será ótimo se você especificar um prefixo que diga que tipo de estilo é e para que é definido. Essa nomeação tornará muito fácil estruturar e estender estilos.
<style name="Theme.MyApp.Main" parent=”...”/> <style name="Widget.MyApp.LoginInputLayout" parent="..."/> <style name="Widget.MyApp.LoginInputLayout.Brown"/> <style name="ThemeOverlay.MyApp.Login" parent=”...”/>
10. Use TextAppearance
Será um bom tom para expandir os estilos básicos do texto e usá-los em qualquer lugar.
Material Design: Typography , Typography Theming .
Conclusão
, — , . - . Material Components. . Sketch — Material Theme Editor . . , .
Material Components GitHub — Modular and customizable Material Design UI components for Android . . , — sample, .
Links úteis: