Fontes em XML, Android O. Configuração e Uso

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Fontes em XML, Android O. Configuração e Uso

Fontes em XML, Android O. Configuração e Uso

Vinícius Thiengo30/08/2017
(1392) (3) (206) (26)
Go-ahead
"Na falta de um foco externo, a mente se volta para dentro de si mesma e cria problemas para resolver, mesmo que os problemas são indefinidos ou sem importância. Se você encontrar um foco, uma meta ambiciosa que parece impossível e força-o a crescer, essas dúvidas desaparecem."
Tim Ferriss
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
Black Week
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas+ 124
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas934
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Acessar Livro
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, tudo bem?

Neste artigo vamos estudar e utilizar a nova API de fontes do Android, digo, o novo tipo de recurso disponível desde o preview do Android 8, Oreo, oficialmente lançado em 21 de agosto de 2017.

Aqui vamos construir um aplicativo laboratório, mais precisamente uma Pokédex, onde será necessário o trabalho com ao menos duas fontes, além das já disponíveis na plataforma.

Nesta primeira parte vamos a apresentação de como utilizar a API, incluindo a versão de suporte; aonde obter fontes open source; como converte-las para .ttf; e alguns pontos, positivos e negativos, dessa nova maneira de trabalhar fontes no dev Android.

Na parte dois, que será lançada posteriormente em um novo artigo, com vídeo, vamos a um conteúdo mais avançado sobre como personalizar fontes em áreas específicas do app.

Abaixo os tópicos que estaremos abordando:

Fonts in XML

Primeiro saiba que a possibilidade de uso de família de fontes, além da conhecida Roboto, já era possível no Android, isso utilizando a mesma API que ainda manteremos o uso junto a nova maneira de definição de fontes em XML, a API Typeface.

O problema é que a facilidade esperada, ainda mais se você é um desenvolvedor Web, essa facilidade não existia, digo, um simples atributo em qualquer View de texto, ou até mesmo na definição de estilo do aplicativo, onde os desenvolvedores simplesmente referenciariam as fontes baixadas para o projeto.

E sim, isso é o que o "Fonts in XML" permite no Android: a fácil atribuição de famílias de fontes aos componentes visuais de texto, até mesmo SpannableStrings conseguem fazer uso dessas, digo, das famílias de fontes, pois em código o Typeface ainda será necessário.

Configuração inicial

Inicialmente você precisa da SDK Plataform API 26 instalada em seu Android Studio. Para isso:

  1. Abra o SDK Manager;
  2. Clique na aba SDK Plataforms;
  3. Dê um check ✔ em Android 8 ou qualquer uma outra API superior. Isso assumindo que você ainda já não tenha está API em sua instalação do Android Studio;
  4. Clique em Apply ou Ok;
  5. Reinicie o Android Studio.

Caso os passos acima não sejam familiares a ti, algo comum se você estiver iniciando no desenvolvimento Android, não deixe de ver o artigo, com vídeo, sobre o Android Studio que tenho em: Android Studio: Instalação, Configuração e Otimização.

Por fim, como configuração inicial, terá de criar um folder /font em /res:

Com a visualização da janela de projeto em "Android", expanda o módulo /app;

  1. Clique com o botão direito do mouse em /res;
  2. Vá em New;
  3. Logo depois clique em Android resource directory;
  4. No menu drop down, Resource type, selecione font;
  5. Clique em Ok.

Agora é somente colocar os arquivos de fonte, .ttf, .ttc ou .otf, dentro de /font.

Somente esses três tipos de arquivos são possíveis?

Não. Você também pode colocar um .xml de <font-family>.

Baixando fontes

Há inúmeros repositórios online com fontes open source. O que eu utilizo e também é recomendado como fonte, de família de fontes, na documentação é o Google Fonts.

Mesmo também sendo possível a customização da fonte escolhida, o ponto negativo do Google Fonts é que não há uma simples opção "Download font", temos de acessar o CSS e posteriormente o arquivo .woff2 e então, depois de baixa-lo, converte-lo para .ttf ou .otf.

A seguir um exemplo de como proceder o download da font "Indie Flower":

  • Na caixa de busca coloque "Indie Flower";
  • Selecione ela clicando no ícone +;
  • Clique na caixa de fundo que irá surgir e então copie toda a URL que está no atributo href da tag <link> de exemplo. Aqui o valor era: https://fonts.googleapis.com/css?family=Indie+Flower;

  • Cole esse URL na caixa de endereços de seu navegador e então tente abrir o arquivo CSS;
  • Haverá ao menos um bloco de código. Independente da quantidade de blocos, você terá de copiar o URL presente em url() do bloco /* latin */;
/* latin */
@font-face {
font-family: 'Indie Flower';
font-style: normal;
font-weight: 400;
src: local('Indie Flower'), local('IndieFlower'), url(https://fonts.gstatic.com/s/indieflower/v8/10JVD_humAd5zP2yrFqw6hampu5_7CjHW5spxoeN3Vs.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
  • Com a URL já copiada, volte a barra de endereços de seu navegador, cole ela e tente acessar. Será realizado o download do arquivo .woff2.

Assim finalizamos o processo de baixar uma fonte do Google. Outros sites têm este procedimento facilitado, oferencendo um botão de download.

Uma outra vantagem em permanecer com o uso do Google Fonts é que ali você sabe que todas as fontes são open source e que há uma grande variedade delas.

Note que enfatizei sobre o download de fontes em versão latin, pois em meus testes nenhuma das outras, baixadas de outros blocos, funcionou.

Agora vamos a conversão, pois .woff2 não é aceito como tipo válido de fonte no Android, quando você tenta este, recebe a seguinte resposta: "... Error: The file name must end with .xml, .ttf, .ttc or .otf".

Convertendo fontes

Provavelmente você deve ter o mesmo hábito: na necessidade de converter algo, acessa o Google com o seguinte questionamento "FormatoX to FormatoY". E assim uma série de conversores online, gratuitos, aparecem.

Para conversão de fontes recomendo o Every Thing Fonts. Acessando o menu e indo em Font conversion, logo depois em woff conversions e por fim clicando em woff2 to ttf, temos a página de conversão:

  • Clique em Pick Font File;
  • Selecione a fonte que baixou na seção anterior;
  • Em The EULAs of the font allow this conversion coloque Yes;
  • Clique em Convert.

Copie sua nova fonte .ttf e cole no folder /font de seu projeto Android. O nome da fonte não poderá ter caracteres especiais, traço (-) e espaço em branco.

Pré-visualização da fonte

Indo em /res/font e clicando na nova fonte adicionada você consegue ter um preview dela: 

Com isso vamos a parte de uso das APIs de fontes.

Definição do atributo fontFamily

Em qualquer TextView, ou em componentes visuais que herdem desta View, você pode utilizar o atributo fontFamily, como a seguir:

...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:fontFamily="@font/indie_flower"
android:text="Apenas um teste" />
...

 

Este formato funciona a partir da API 14, que segundo o chart Android: corresponde a quase 100% de todos os devices Android no mercado.

Note a necessidade do uso de @font para referenciar fontes presentes no folder /res/font. Caso você vá utilizar alguma família de fontes já presente na plataforma, sans-serif, por exemplo, não se utiliza uma referência a folder, @font, somente o nome da família de fontes.

É possível utilizar a tela de Design do Android Studio para selecionar a fonte. Todas presentes em /res/font estarão presentes também do drop down de seleção. Para isso, siga:

  • Clique na aba Design;
  • Selecione o componente visual que deseja alterar a família de fonte;
  • No menu direito vá em fontFamily e abra o menu drop down;
  • Escolha a fonte de sua preferência.

Criando o próprio arquivo de família de fontes

Podemos criar nosso próprio arquivo XML de família de fontes referenciando outras fontes e definido um novo conjunto de estilo a elas. O arquivo deve ficar em /res/font.

Segue um exemplo:

<?xml version="1.0" encoding="utf-8"?>
<font-family
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<font
android:font="@font/indie_flower"
android:fontStyle="normal"
android:fontWeight="700"
app:font="@font/indie_flower"
app:fontStyle="normal"
app:fontWeight="700"/>

<font
android:font="@font/indie_flower"
android:fontStyle="italic"
android:fontWeight="400"
app:font="@font/indie_flower"
app:fontStyle="italic"
app:fontWeight="400"/>
</font-family>

 

Apesar de existirem definições para duas fontes, podemos ter somente uma definição <font>.

A seguir a explicação dos atributos:

  • font: a referência a alguma fonte presente no folder /font, podendo ser dos seguintes tipos: .xml, .ttf, .otf e .ttc.
  • fontStyle: pode ser italic ou normal. Não tem um valor padrão, é de acordo com o já definido na fonte referenciada em font. O valor definido em fontStyle sobrescreve o valor da fonte referenciada somente se está aceitar o estilo definido. Caso a fonte não suporte italic, este estilo não será aplicado;
  • fontWeight: como fontStyle, sobrescreve o valor padrão da referência em font somente se ela aceitar o valor definido, este que pode variar entre 100 e 900, sendo que o valor deve ser múltiplo de 100.

O namespace app: é necessário para que o arquivo de fonte funcione também em APIs abaixo da 26, Android Oreo.

O uso em código da família de fontes personalizada é como o de uma fonte individual, referenciando pelo nome e partindo do folder /font.

Você provavelmente deve estar se perguntando: qual fonte será utilizada quando houver mais de uma em nosso arquivo de família de fontes?

Como resposta deixo o texto da documentação (em tradução livre): "Quando você usa uma família de fontes, o TextView (ou subclasse) muda sozinho, conforme necessário, para usar os arquivos de fontes dessa família.".

Ou seja, nós desenvolvedores não temos controle sobre essa escolha. Logo, como minha opinião, vejo como prudente não definir mais de uma fonte em sua família de fontes personalizada.

Fiz alguns testes, mudando o posicionamento das tags <font> e também realizando o uso do atributo fontFamily junto ao atributo textStyle, por exemplo, para ver se haveria alguma influência, mas não houve.

O sistema é realmente quem defini qual fonte será utilizada, ao menos até a versão utilizada na construção deste artigo.

Definindo fontes via código de programação

Para a definição de fontes, em tempo de execução, em seu algoritmo Kotlin:

...
textView.typeface = resources.getFont( R.font.indie_flower )
...

 

Em Java seria:

...
textView.setTypeface( getResources().getFont( R.font.indie_flower ) );
...

Definindo fonte padrão no tema do aplicativo

A outra maneira de definir fonte é por meio do arquivo /res/values/styles.xml do projeto. Ali definimos a fonte que por padrão será utilizada em quase todos os componentes visuais, de texto, do aplicativo.

A seguir um exemplo de como definir a fonte no tema do projeto:

...
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
<item name="android:fontFamily">@font/indie_flower</item>
</style>
...

 

Note que o fontFamily do arquivo styles.xml, diferente dos de layout, tem o suporte mínimo a partir da API 16, sendo que na documentação informa sobre o suporte mínimo sendo a partir da API 14.

Não vejo isso como algo preocupante, pois a partir da API 16, até a construção deste artigo, 99.2% de todos os devices Android no mercado eram atendidos pelo aplicativo com minApi em 16.

Uma outra maneira de colocar fontFamily no arquivo de definição de tema é criando um <style> somente para fonte e então utiliza-lo nos atributos TextAppearance de componentes que têm um ou mais TextView, ou subclasse dessa, interno, mas não têm o atributo fontFamily disponível.

Segue exemplo de um novo estilo, somente para uso de fonte:

...
<style name="fonteCustomizada" parent="@android:style/TextAppearance">
<item name="android:fontFamily">@font/indie_flower</item>
</style>
...

 

Em um NavigationView o <style> acima seria utilizado da seguinte maneira:

...
<android.support.design.widget.NavigationView
app:itemTextAppearance="@style/fonteCustomizada" />
...

 

Ok, entendi, mas por que, no início da seção, você falou "quase todos os componentes visuais"?

Porque em alguns, mesmo com a definição de fonte no código do tema, o estilo não funciona. O componente Toolbar é um desses. Nele temos de definir a fonte por outros meios, pelo atributo android:titleTextAppearance, por exemplo.

API de suporte

A API de suporte para fontes vem junto ao Android Support Library 26.0, logo, não deixe de atualizar a disponível em seu Android Studio:

  • Abra o SDK Manager;
  • Clique na aba SDK Tools;
  • Expanda Support Repository;
  • Selecione Android Support Repository. Caso este não esteja atualizado, sem o check ✔;
  • Clique em Apply ou Ok.

Todas as APIs de suporte estão neste SDK Tool que será colocado no folder do Android SDK.

Logo depois o recomendado é adicionar a referência a API support compat, isso em seu gradle de módulo app:

...
dependencies {
...
compile "com.android.support:support-compat:26.0.1"
}
...

 

Dependendo das referências que você já tiver em seu projeto, as APIs de suporte para fontes já estarão presentes, sem a necessidade da referência explícita acima. Um exemplo é a API de design: 'com.android.support:design:26.0.1'.

Depois para fazer uso, o indicado é, em XML, utilizar o namespace app: junto ao android:, isso no arquivo de definição de uma nova família de fontes, como no exemplo a seguir:

...
<font
android:font="@font/indie_flower"
android:fontStyle="normal"
android:fontWeight="700"
app:font="@font/indie_flower"
app:fontStyle="normal"
app:fontWeight="700"/>
...

 

Segundo meus testes, ao menos a partir da API 16, em layouts XML utilizando o atributo fontFamily, não há necessidade de utilizar o namespace app:, aliás nem mesmo haverá suporte para isso, digo, suporte para app:fontFamily.

Para acesso a uma família de fonte em código de programação, utilize a classe ResourcesCompat. Segue exemplo em Kotlin:

...
textView.typeface = ResourcesCompat.getFont( this@MainActivity, R.font.indie_flower )
...

 

Agora em Java:

...
textView.setTypeface( ResourcesCompat.getFont( MainActivity.this, R.font.indie_flower ) )
...

Projeto Android

Depois de apresentada a nova maneira de definição de fontes, vamos a um projeto similar ao que teríamos no mundo real, um que necessite de fontes extras, além das já disponíveis pelo Android.

Vamos utilizar um aplicativo de Pokédex, simples, com poucos pokémons, mas que exige o uso das fontes bem conhecidas do desenho animado. Como exemplo:

O projeto completo pode ser encontrado no GitHub a seguir: https://github.com/viniciusthiengo/pokedex. No decorrer do artigo vamos construí-lo passo a passo.

Vamos iniciar apresentando o aplicativo sem nenhuma fonte personalizada e posteriormente vamos as atualizações.

Crie um novo projeto no Android Studio com o nome "Pokédex". O projeto deve ser um Kotlin. Como activity inicial escolha a que contém o menu gaveta. Para API mínima escolha a 16.

Ao final desta primeira parte teremos o seguinte aplicativo:

E a seguinte estrutura de projeto:

Configurações Gradle

A seguir as configurações do Gradle nível de projeto, build.gradle (Project: Pokdex):

buildscript {
ext.kotlin_version = '1.1.4-2'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-beta2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

Abaixo as configurações do Gradle de nível de módulo, build.gradle (Module: app):

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "thiengo.com.br.pokdex"
minSdkVersion 16
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

/* PARA RecyclerView E OUTROS COMO O CoordinatorLayout */
implementation 'com.android.support:design:26.0.1'

/* PARA TRABALHO COMPLETO COM O CardView */
implementation 'com.android.support:cardview-v7:26.0.1'
}

 

Aqui vamos manter ambas as versões do Gradle sem alterações. Você pode notar que removi uma série de referências do build.gradle (Module: app), isso para aliviar as sincronizações em ambiente de desenvolvimento e permitir que o IDE trabalhe de maneira mais eficiente.

Caso queira saber mais sobre o Gradle, acesse, posteriormente a este conteúdo, o link a seguir: Android Studio: Gradle.

Configurações AndroidManifest

A seguir a configuração inicial do AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="thiengo.com.br.pokdex">

<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".PokemonsActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".DetalhesActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>

Configurações de estilo

A seguir os arquivos de estilos, de Strings e de dimensões do projeto. Iniciando com o arquivo de definição de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#f44336</color>
<color name="colorPrimaryDark">#ba000d</color>
<color name="colorAccent">#bdbdbd</color>
<color name="colorTitleText">#999999</color>
</resources>

 

Em seguida o arquivo de dimensões comuns ao projeto, /res/values/dimes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">4dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="app_bar_height">320dp</dimen>
<dimen name="title_text_size">24sp</dimen>
</resources>

 

Abaixo o arquivo /res/values/drawables.xml para o trabalho com ícones oferecidos pelo sistema, algo que já vem configurado com um novo projeto Android com uma atividade contendo o menu gaveta:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item>
<item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item>
</resources>

 

O arquivo acima teria algumas outras definições, mas aqui vamos utilizar somente dois ícones no menu gaveta, os dois de subitem que já vêm em um novo projeto.

Assim o arquivo de definição de Strings, /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pokédex</string>
<string name="navigation_drawer_open">Abrir menu gaveta</string>
<string name="navigation_drawer_close">Fechar menu gaveta</string>
<string name="action_settings">Configurações</string>
</resources>

 

Por fim o arquivo de definição de estilo, tema, do projeto, /res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@drawable/background_pokedex</item>
</style>

<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

 

Note que aqui no artigo as limitações de conteúdo estão por parte das imagens de background, profile, itens e ícones do projeto. Para acesso a essas você deverá entrar no /res do projeto no GitHub.

Abaixo a versão para API 21, ou posterior, do arquivo styles, /res/values-v21/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

Classe de domínio

Em seu projeto, crie um package /domain, nele teremos uma classe de domínio.

A seguir a classe Pokemon:

class Pokemon(
val numero: Int,
val nome: String,
val imagemRes: Int,
val tipo: Array<String>,
val descricao: String,
val altura: String,
val peso: String,
val sexo: String,
val categoria: String,
val habilidades: Array<String>,
val fraquezas: Array<String>) : Parcelable {

fun getTipoFormatado() = tipo.joinToString(", ")

fun getHabilidadesFormatado() = habilidades.joinToString(", ")

fun getFraquezasFormatado() = fraquezas.joinToString(", ")

constructor(source: Parcel) : this(
source.readInt(),
source.readString(),
source.readInt(),
source.createStringArray(),
source.readString(),
source.readString(),
source.readString(),
source.readString(),
source.readString(),
source.createStringArray(),
source.createStringArray()
)

override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeInt(numero)
writeString(nome)
writeInt(imagemRes)
writeStringArray(tipo)
writeString(descricao)
writeString(altura)
writeString(peso)
writeString(sexo)
writeString(categoria)
writeStringArray(habilidades)
writeStringArray(fraquezas)
}

companion object {
@JvmField val KEY = "pokemon"

@JvmField
val CREATOR: Parcelable.Creator<Pokemon> = object : Parcelable.Creator<Pokemon> {
override fun createFromParcel(source: Parcel): Pokemon = Pokemon(source)
override fun newArray(size: Int): Array<Pokemon?> = arrayOfNulls(size)
}
}
}

 

Note que já estamos implementando a Interface Parcelable, pois no projeto inicial já é enviado ao menos um objeto Pokemon para a atividade de detalhes do aplicativo, envio realizado via Intent.

Camada de dados, classe Mock

Para este aplicativo de exemplo, para facilita-lo, vamos utilizar uma classe de dados simulados, mock data, onde facilmente poderemos obter uma lista de pokémons.

Segue classe Mock:

class Mock {
companion object {
fun getPokemons() = listOf<Pokemon>(
Pokemon(
1,
"Bulbasaur",
R.drawable.bulbasaur,
arrayOf("Grama", "Venenoso"),
"Bulbasaur can be seen napping in bright sunlight. There is a seed on its back. By soaking up the sun's rays, the seed grows progressively larger.",
"0,7 m",
"6,9 kg",
"Masculino / feminino",
"Semente",
arrayOf("Overgrow"),
arrayOf("Fogo", "Voador", "Gelo", "Psíquico")),
Pokemon(
2,
"Ivysaur",
R.drawable.ivysaur,
arrayOf("Grama", "Venenoso"),
"There is a bud on this Pokémon's back. To support its weight, Ivysaur's legs and trunk grow thick and strong. If it starts spending more time lying in the sunlight, it's a sign that the bud will bloom into a large flower soon.",
"1,0 m",
"13,0 kg",
"Masculino / feminino",
"Semente",
arrayOf("Overgrow"),
arrayOf("Fogo", "Voador", "Gelo", "Psíquico")),
Pokemon(
3,
"Venusaur",
R.drawable.venusaur,
arrayOf("Grama", "Venenoso"),
"There is a large flower on Venusaur's back. The flower is said to take on vivid colors if it gets plenty of nutrition and sunlight. The flower's aroma soothes the emotions of people.",
"2,0 m",
"100,0 kg",
"Masculino / feminino",
"Semente",
arrayOf("Overgrow"),
arrayOf("Fogo", "Voador", "Gelo", "Psíquico")),
Pokemon(
4,
"Charmander",
R.drawable.charmander,
arrayOf("Fogo"),
"The flame that burns at the tip of its tail is an indication of its emotions. The flame wavers when Charmander is enjoying itself. If the Pokémon becomes enraged, the flame burns fiercely.",
"0,6 m",
"8,5 kg",
"Masculino / feminino",
"Lagarto",
arrayOf("Blaze"),
arrayOf("Solo", "Pedra", "Água")),
Pokemon(
5,
"Charmeleon",
R.drawable.charmeleon,
arrayOf("Fogo"),
"Charmeleon mercilessly destroys its foes using its sharp claws. If it encounters a strong foe, it turns aggressive. In this excited state, the flame at the tip of its tail flares with a bluish white color.",
"1,1 m",
"19,0 kg",
"Masculino / feminino",
"Chama",
arrayOf("Blaze"),
arrayOf("Solo", "Pedra", "Água")),
Pokemon(
6,
"Charizard",
R.drawable.charizard,
arrayOf("Fogo", "Voador"),
"Charizard flies around the sky in search of powerful opponents. It breathes fire of such great heat that it melts anything. However, it never turns its fiery breath on any opponent weaker than itself.",
"1,7 m",
"90,5 kg",
"Masculino / feminino",
"Chama",
arrayOf("Blaze"),
arrayOf("Pedra", "Elétrico", "Água")),
Pokemon(
7,
"Squirtle",
R.drawable.squirtle,
arrayOf("Água"),
"Squirtle's shell is not merely used for protection. The shell's rounded shape and the grooves on its surface help minimize resistance in water, enabling this Pokémon to swim at high speeds.",
"0,5 m",
"9,0 kg",
"Masculino / feminino",
"Tartaruga pequena",
arrayOf("Torrent"),
arrayOf("Elétrico", "Grama")),
Pokemon(
8,
"Wartortle",
R.drawable.wartortle,
arrayOf("Água"),
"Its tail is large and covered with a rich, thick fur. The tail becomes increasingly deeper in color as Wartortle ages. The scratches on its shell are evidence of this Pokémon's toughness as a battler.",
"1,0 m",
"22,5 kg",
"Masculino / feminino",
"Tartaruga",
arrayOf("Torrent"),
arrayOf("Elétrico", "Grama")),
Pokemon(
9,
"Blastoise",
R.drawable.blastoise,
arrayOf("Água"),
"Blastoise has water spouts that protrude from its shell. The water spouts are very accurate. They can shoot bullets of water with enough accuracy to strike empty cans from a distance of over 160 feet.",
"1,6 m",
"85,5 kg",
"Masculino / feminino",
"Marisco",
arrayOf("Torrent"),
arrayOf("Elétrico", "Grama")),
Pokemon(
10,
"Caterpie",
R.drawable.caterpie,
arrayOf("Inseto"),
"Caterpie has a voracious appetite. It can devour leaves bigger than its body right before your eyes. From its antenna, this Pokémon releases a terrifically strong odor.",
"0,3 m",
"2,9 kg",
"Masculino / feminino",
"Minhoca",
arrayOf("Shield Dust"),
arrayOf("Fogo", "Voador", "Pedra")),
Pokemon(
11,
"Metapod",
R.drawable.metapod,
arrayOf("Inseto"),
"The shell covering this Pokémon's body is as hard as an iron slab. Metapod does not move very much. It stays still because it is preparing its soft innards for evolution inside the hard shell.",
"0,7 m",
"9,9 kg",
"Masculino / feminino",
"Casulo",
arrayOf("Shed Skin"),
arrayOf("Fogo", "Voador", "Pedra")),
Pokemon(
12,
"Butterfree",
R.drawable.butterfree,
arrayOf("Inseto", "Voador"),
"Butterfree has a superior ability to search for delicious honey from flowers. It can even search out, extract, and carry honey from flowers that are blooming over six miles from its nest.",
"1,1 m",
"32,0 kg",
"Masculino / feminino",
"Borboleta",
arrayOf("Compound Eyes"),
arrayOf("Fogo", "Voador", "Pedra", "Elétrico", "Gelo"))
)
}
}

 

Ela é um pouco grande, mas somente tem criação de objetos, nada de lógica de negócio. Lembrando que a classe acima ficará no pacote /data do projeto.

Classe adaptadora

Na atividade principal estaremos fazendo uso do framework de lista RecyclerView, para isso teremos a classe adaptadora PokemonsAdapter.

A seguir o layout de item utilizado nesta classe, /res/layout/pokemon_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cv_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:elevation="4dp"
app:cardCornerRadius="2dp"
app:cardElevation="4dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
card_view:cardBackgroundColor="@android:color/white"
tools:context="thiengo.com.br.pokdex.PokemonsActivity"
tools:showIn="@layout/app_bar_pokemons">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">

<ImageView
android:id="@+id/iv_pokemon"
android:layout_width="wrap_content"
android:layout_height="90dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:scaleType="fitCenter" />

<TextView
android:id="@+id/tv_numero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/iv_pokemon"
android:layout_centerHorizontal="true"
android:textSize="18sp" />

<TextView
android:id="@+id/tv_nome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_numero"
android:layout_centerHorizontal="true"
android:textSize="16sp" />

<TextView
android:id="@+id/tv_tipo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_nome"
android:layout_marginTop="4dp"
android:textSize="12sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>

 

Abaixo o diagrama do layout anterior:

Então o código Kotlin da classe PokemonsAdapter:

class PokemonsAdapter(
private val context: Context,
private val pokemons: List<Pokemon>) :
RecyclerView.Adapter<PokemonsAdapter.ViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int) : PokemonsAdapter.ViewHolder {

val v = LayoutInflater
.from(context)
.inflate(R.layout.pokemon_item, parent, false)

return ViewHolder(v)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setData(pokemons[position])
}

override fun getItemCount(): Int {
return pokemons.size
}

inner class ViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView),
View.OnClickListener {

var ivPokemon: ImageView
var tvNumero: TextView
var tvNome: TextView
var tvTipo: TextView

init {
itemView.setOnClickListener(this)
ivPokemon = itemView.findViewById(R.id.iv_pokemon)
tvNumero = itemView.findViewById(R.id.tv_numero)
tvNome = itemView.findViewById(R.id.tv_nome)
tvTipo = itemView.findViewById(R.id.tv_tipo)
}

fun setData(pokemon: Pokemon) {
ivPokemon.setImageResource( pokemon.imagemRes )
tvNumero.text = pokemon.numero.toString().padStart(3, '0')
tvNome.text = pokemon.nome
tvTipo.text = pokemon.getTipoFormatado()
}

override fun onClick(view: View?) {
val intent = Intent(context, DetalhesActivity::class.java)
intent.putExtra(Pokemon.KEY, pokemons[adapterPosition])
context.startActivity(intent)
}
}
}

 

De todo o código boilerplate apresentado acima, o único que é novidade aqui no Blog é o método padStart() que tem como finalidade preencher o início de uma String com caracteres que completem o tamanho informado, tamanho presente no primeiro argumento do método.

Aqui, com o número 1 sendo a String alvo, o tamanho mínimo informado no primeiro argumento de padStart() sendo 3, com o segundo argumento, o caractere, sendo '0', temos como resultado: "001".

Atividade principal

Nossa atividade principal, PakemonsActivity, é simples e mantém quase todo o código inicial de um novo projeto Android com um DrawerLayout.

Removi alguns métodos e referências que não seriam utilizados e coloquei o framework de lista, RecyclerView, para completar o conteúdo.

A seguir o layout de conteúdo desta atividade, /res/layout/app_bar_pokemons.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_pokeball"
tools:context="thiengo.com.br.pokdex.PokemonsActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_pokemons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_info" />

</android.support.design.widget.CoordinatorLayout>

 

Abaixo o diagrama do XML anterior:

Agora o arquivo XML de definição de header do menu gaveta, /res/layout/nav_header_pokemons.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">

<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:scaleType="fitCenter"
app:srcCompat="@drawable/profile" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="Ash Ketchum"
android:textSize="20sp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ash.ketchum@thiengo.com.br" />
</LinearLayout>

 

Então o diagrama do trecho XML anterior: 

Agora o código XML de itens do menu gaveta, /res/menu/activity_pokemons_drawer.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">

<group android:checkableBehavior="single">
<item
android:id="@+id/cat_00"
android:checked="true"
android:title="Todos" />
<item
android:id="@+id/cat_01"
android:title="Elétrico" />
<item
android:id="@+id/cat_02"
android:title="Fogo" />
<item
android:id="@+id/cat_03"
android:title="Grama" />
<item
android:id="@+id/cat_04"
android:title="Inseto" />
</group>

<item android:title="Interno">
<menu>
<item
android:id="@+id/config"
android:icon="@drawable/ic_menu_share"
android:title="Meus compartilhamentos" />
<item
android:id="@+id/contato"
android:icon="@drawable/ic_menu_send"
android:title="Contato equipe app" />
</menu>
</item>
</menu>

 

Segue diagrama do XML de menu: 

E por fim o layout principal de PokemonsActivity, o que junta todas as peças, /res/layout/activity_pokemons.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">

<include
layout="@layout/app_bar_pokemons"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/white"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_pokemons"
app:itemTextColor="#666"
app:menu="@menu/activity_pokemons_drawer" />

</android.support.v4.widget.DrawerLayout>

 

Segue diagrama do layout anterior:

E então o código Kotlin da atividade principal do projeto:

class PokemonsActivity :
AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pokemons)
setSupportActionBar(toolbar)

fab.setOnClickListener {
Snackbar
.make(
it,
"Ainda falta implementar uma ação",
Snackbar.LENGTH_LONG)
.setAction("Ação", null).show()
}

val toggle = ActionBarDrawerToggle(
this,
drawer_layout,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()

initRecycler()
}

private fun initRecycler() {
rv_pokemons.setHasFixedSize(true)

val mLayoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
rv_pokemons.layoutManager = mLayoutManager

rv_pokemons.adapter = PokemonsAdapter(this, Mock.getPokemons())
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.pokemons, menu)
return true
}

override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
}
else {
super.onBackPressed()
}
}
}

Atividade de detalhes

Para finalizar a apresentação inicial do projeto, temos a atividade de detalhes. Começando pelo trecho de layout de conteúdo, /res/layout/content_detalhes.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".DetalhesActivity"
tools:showIn="@layout/activity_detalhes">

<RelativeLayout
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">

<TextView
android:id="@+id/tv_t_descricao"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="10dp"
android:text="Descrição"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_descricao"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_descricao"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_numero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_descricao"
android:layout_marginTop="10dp"
android:text="Número"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_numero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_numero"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_altura"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_numero"
android:layout_marginTop="10dp"
android:text="Altura"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_altura"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_altura"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_peso"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_altura"
android:layout_marginTop="10dp"
android:text="Peso"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_peso"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_peso"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_sexo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_peso"
android:layout_marginTop="10dp"
android:text="Sexo"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_sexo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_sexo"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_categoria"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_sexo"
android:layout_marginTop="10dp"
android:text="Categoria"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_categoria"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_categoria"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_habilidades"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_categoria"
android:layout_marginTop="10dp"
android:text="Principais habilidades"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_habilidades"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_habilidades"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_tipo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_habilidades"
android:layout_marginTop="10dp"
android:text="Tipo"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_tipo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_tipo"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />

<TextView
android:id="@+id/tv_t_fraquezas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_tipo"
android:layout_marginTop="10dp"
android:text="Fraquezas"
android:textColor="@color/colorTitleText"
android:textSize="@dimen/title_text_size" />

<TextView
android:id="@+id/tv_fraquezas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_t_fraquezas"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

A seguir o diagrama do layout anterior:

Assim o trecho principal do layout de DetalhesActivity, /res/layout/activity_detalhes.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fitsSystemWindows="true"
tools:context=".DetalhesActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/ct_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:padding="35dp"
android:scaleType="fitCenter"
android:src="@drawable/bulbasaur"
app:layout_collapseMode="parallax" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_detalhes" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_share" />
</android.support.design.widget.CoordinatorLayout>

 

Abaixo o diagrama do layout anterior:

A seguir a imagem de como está, até aqui, a tela de detalhes do app:

E por fim o código Kotlin de DetalhesActivity:

class DetalhesActivity :
AppCompatActivity() {

var pokemon : Pokemon? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detalhes)
setSupportActionBar(toolbar)

getSupportActionBar()?.setDisplayHomeAsUpEnabled(true)
getSupportActionBar()?.setDisplayShowHomeEnabled(true)

fab.setOnClickListener {
Toast.makeText(
this@DetalhesActivity,
"Ainda falta implementar esta parte",
Toast.LENGTH_SHORT)
}

pokemon = intent.getParcelableExtra<Pokemon>(Pokemon.KEY)
iv_header.setImageResource( pokemon?.imagemRes ?: 0 )
tv_descricao.text = pokemon?.descricao
tv_numero.text = pokemon?.numero.toString().padStart(3, '0')
tv_altura.text = pokemon?.altura
tv_peso.text = pokemon?.peso
tv_sexo.text = pokemon?.sexo
tv_categoria.text = pokemon?.categoria
tv_habilidades.text = pokemon?.getHabilidadesFormatado()
tv_tipo.text = pokemon?.getTipoFormatado()
tv_fraquezas.text = pokemon?.getFraquezasFormatado()
}

override fun onResume() {
super.onResume()
toolbar.title = pokemon?.nome
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.getItemId()
if (id == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
}

Assim podemos ir as atualizações no projeto.

Atualização das famílias de fontes do projeto

A seguir o roteiro que sequiremos para realizar a atualização das fontes do projeto:

  • Primeiro definir quais partes do app receberão atualização de fonte;
  • Depois o download das fontes;
  • Por fim atualizar o código para a aplicação das novas fontes.

Definindo os trechos que receberão atualização

Como a fonte do anime Pokémon é famosa, vamos optar por utiliza-la em todo o aplicativo. Exceto nas partes onde há texto informativo sobre os pokémons e alguns trechos com dados do usuário conectado ao app, ou seja:

  • Na definição de nome e tipo de pokémon quando na lista de cards;
  • Nos conteúdos textuais da atividade de detalhes;
  • Na apresentação do email do usuário conectado.

Os itens acima foram escolhidos como não atualizáveis principalmente por serem trechos onde o tamanho convencional de fonte será mantido, ou seja, um que fica pouco legível quando utilizando as fontes do anime.

Download fontes

Realizando algumas buscas encontrei um site que contém dois tipos de fontes do anime: uma preenchida e outra somente com bordas.

O site é o seguinte: http://www.dafont.com/pt/pokemon.font. Entre nele, deixe a fonte com escolha "Médio" e assim clique em "Baixar".

Você realizará o download de um .zip. Descompacte ele e então atualize os nomes das fontes Pokemon Hollow.ttfPokemon Solid.ttf para, respectivamente, pokemon_hollow.ttf e pokemon_solid.ttf.

Crie o folder /font dentro de /res e então copie as fontes para dentro dele. 

Definindo a fonte padrão do projeto

Primeiro temos de ter em mente os locais onde não conseguiremos trocar de fonte, não de maneira trivial, e então escolher que tipo de fonte queremos nestes locais para então definir está família de fontes como sendo a padrão do projeto.

No Android há uma limitação quanto a mudança de fonte de itens do menu de barra de topo:

Não é possível utilizar os atributos textAppearance e fontFamily e nem mesmo o acesso da String para modificação via SpannableString.

Para este trecho do aplicativo queremos que a fonte pokemon_hollow.ttf seja aplicada:

Assim vamos colocar ela como a fonte padrão do projeto, mais precisamente atualize o arquivo /res/values/styles.xml como a seguir:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
<item name="android:fontFamily">@font/pokemon_hollow</item>
</style>
...
</resources>

 

Executando o aplicativo, temos:

E no menu de topo:

Em alguns pontos é quase impossível entender o conteúdo. E veja que o texto da barra de topo não recebe a fonte definida.

Vamos as atualizações de outos pontos, incluindo do texto do Toolbar e do CollapsingToolbarLayout.

Textos de informação nos cards

Para os textos presentes nos CardViews, vamos atualizar o nome do pokémon e os tipos dele para que nestes trechos uma fonte sem serifas seja utilizada.

Em /res/layout/pokemon_item.xml realize as alterações em destaque:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView ...>

<RelativeLayout ...>
...

<TextView
android:id="@+id/tv_nome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_numero"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif"
android:textSize="16sp" />

<TextView
android:id="@+id/tv_tipo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_nome"
android:layout_marginTop="4dp"
android:fontFamily="sans-serif"
android:textSize="12sp" />

</RelativeLayout>
</android.support.v7.widget.CardView>

 

Executando o aplicativo, temos:

Cabeçalho do menu gaveta

Para o header do menu gaveta vamos atualizar as fontes dos dois textos, o nome do usuário e o email dele. O primeiro utilizará a fonte pokemon_solid e o segundo fará uso da fonte nativa do sistema, sans-serif.

Segue atualização no XML /res/layout/nav_header_pokemons.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
...

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/pokemon_solid"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="Ash Ketchum"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="20sp" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:text="ash.ketchum@thiengo.com.br" />
</LinearLayout>

 

Executando o aplicativo e abrindo o menu gaveta, temos:

Título da barra de topo, Toolbar

Como informado anteriormente: o título da barra de topo não recebe a estilização de fonte somente com a definição de família de fonte no tema do aplicativo.

Para esta atualização, podemos acessar o TextView interno ao Toolbar, via programação, e então atualizar o Typeface dele. A outra opção é utilizar o atributo titleTextAppearance para isso.

Caso queira atualizar também a fonte do subtítulo, você tem o atributo subtitleTextAppearance.

Ambos esperam a definição de um style que tenha o trabalho com o TextAppearance do Android.

Logo, no arquivo /res/values/styles.xml adicione o seguinte trecho de código em destaque:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<style name="fontePokemon" parent="@android:style/TextAppearance">
<item name="android:fontFamily">@font/pokemon_solid</item>
</style>
...
</resources>

 

Na tag <Toolbar> de /res/layout/app_bar_pokemons.xml acrescente o atributo em destaque:

...
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:titleTextAppearance="@style/fontePokemon" />
...

 

Não esqueça da definição do namespace app: na tag root do layout:

...
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_pokeball"
tools:context="thiengo.com.br.pokdex.PokemonsActivity">
...

 

Executando o aplicativo, temos:

Título expandido e collapsed, CollapsingToolbarLayout

Mesmo sabendo que temos uma Toolbar dentro do CollapsingToolbarLayout e que temos atributos como: collapsedTitleTextAppearanceexpandedTitleTextAppearance, além dos atributos de TextAppearance do Toolbar.

Mesmo com esses atributos a definição de fonte para os títulos expandido e collapsed do CollapsingToolbarLayout deve ocorrer no código, pelos métodos setCollapsedTitleTypeface() e setExpandedTitleTypeface().

Isso, pois os atributos informados, para trabalho com definição de fontes, não dão resultado algum.

No método onCreate(), da atividade DetalhesActivity, coloque o seguinte código em destaque:

...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detalhes)
setSupportActionBar(toolbar)
...

ct_layout
.setCollapsedTitleTypeface( ResourcesCompat.getFont(this, R.font.pokemon_solid) )
ct_layout
.setExpandedTitleTypeface( ResourcesCompat.getFont(this, R.font.pokemon_solid) )

...
}
...

 

Executando o aplicativo e acionando qualquer um dos pokémons, temos:

Conteúdo da atividade de detalhes

Para está primeira parte de trabalho com fontes em XML, ainda nos falta a atualização das fontes do conteúdo da atividade de detalhes.

Para isso vamos utilizar nos rótulos maiores a fonte pokemon_solid e para os textos de conteúdo a fonte sans-serif.

No arquivo /res/layout/content_detalhes.xml adicione os atributos em destaque:

...
<TextView
android:id="@+id/tv_t_descricao"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_descricao"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_numero"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_numero"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_altura"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_altura"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_peso"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_peso"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_sexo"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_sexo"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_categoria"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_categoria"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_habilidades"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_habilidades"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_tipo"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_tipo"
...
android:fontFamily="sans-serif" />

<TextView
android:id="@+id/tv_t_fraquezas"
...
android:fontFamily="@font/pokemon_solid" />

<TextView
android:id="@+id/tv_fraquezas"
...
android:fontFamily="sans-serif" />
...

 

Executando o aplicativo e acessando a área de detalhes de algum pokémon, temos:

Com isso finalizamos todas as atualizações para essa primeira parte sobre fonts in XML.

No próximo conteúdo estaremos atualizando a fonte de sinalizações Toast, Snackbar e de cada item e sub-item do menu gaveta.

Todas essas atualizações necessitam de nossas "mãos no código", pois definições XML não funcionam aqui, ao menos quando necessitamos de fontes distintas das definidas no arquivo de tema do projeto.

E... se inscreva na lista de emails do Blog, logo ao lado, para receber em primeira mão os conteúdos exclusivos sobre desenvolvimento Android.

Se inscreva também no canal: Thiengo YouTube. Toda semana tem conteúdo novo.

Pontos positivos

  • Simples definição de fonte, em visualizações de texto, com o atributo fontFamily;
  • Definição de fonte padrão pelo arquivo de tema do aplicativo, também utilizando fontFamily;
  • API de suporte já disponível e com modificações mínimas em relação a API oficial.

Pontos negativos

  • Não há uma maneira trivial de definir a fonte de itens e subitens do menu gaveta, digo, uma fonte diferente da definida em styles.xml. Mesmo com o atributo itemTextAppearance presente em NavigationView, não conseguimos o acesso individual aos itens, não com este componente sendo utilizado ao invés de algum framework de lista (RecyclerView, por exemplo);
  • Não é possível definir a fonte individual nas tags de <item> de menu;
  • As APIs de carregamento externo de fonte, Downloadable Fonts, não trazem ganhos reais ao projeto, tendo em mente que dependeremos da qualidade de rede do usuário, dando a possibilidade de "não carregamento" de fontes necessárias ao design do aplicativo. Acrescente a isso que os arquivos de família de fontes não ultrapassam poucos KBs, ou seja, nada que influenciaria consideravelmente o tamanho do APK final.

Slides

Abaixo os slides com a apresentação completa do novo recurso Android, Fonts in XML:

Vídeo

Abaixo o vídeo com a implementação passo a passo da customização de fontes no aplicativo Pekédex:

Para acesso ao projeto completo, entre no GitHub a seguir: https://github.com/viniciusthiengo/pokedex.

Conclusão

Como comentado sobre, no conteúdo de Material Design, aqui do Blog: o controle sobre quais fontes utilizar em um aplicativo, mesmo definindo a padrão nas especificações, seria difícil por parte do Google, isso, pois muitos apps dependem da fonte correta para completar o design.

A possibilidade de definição de fontes com um simples atributo vem facilitar em muito está necessidade de fontes extras nos aplicativos Android.

Ainda faltam muitas melhorias, por exemplo: componentes que não são em sua essência de texto, mas que permitem o uso deste, deveriam ter também a possibilidade de uso do atributo fontFamily. Itens de menu, por exemplo.

Mas temos de ter em mente que a liberação da API XML para fontes veio somente com o Android 8, desde a versão preview. Provavelmente veremos algumas melhorias nesta interface pública.

Não deixe de comentar o que achou, ou a sua dúvida, logo abaixo. E não esqueça também de se inscrever na lista de emails do Blog para receber os conteúdos semanais, exclusivos.

Abraço.

Fontes

Downloadable Fonts

Get Started with the Google Fonts for Android (beta)

Fonts in XML

Font Resources

Configuração da Biblioteca de Suporte

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

Kotlin Android, Entendendo e Primeiro ProjetoKotlin Android, Entendendo e Primeiro ProjetoAndroid
Como Criar Protótipos AndroidComo Criar Protótipos AndroidAndroid
Material DesignMaterial DesignAndroid
Android Studio: Instalação, Configuração e OtimizaçãoAndroid Studio: Instalação, Configuração e OtimizaçãoAndroid

Compartilhar

Comentários Facebook (3)

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...