Definindo Fontes em Trechos Não Triviais do Android

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 /Definindo Fontes em Trechos Não Triviais do Android

Definindo Fontes em Trechos Não Triviais do Android

Vinícius Thiengo05/09/2017
(684) (132) (15)
Go-ahead
"O que as pessoas devem tentar fazer é trabalhar no que gostam, procurando áreas em que sua vocação e talento façam diferença. É fundamental ser paciente. É vital manter o otimismo sempre."
Carlos Slim
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 as atualizações de família de fontes em trechos do aplicativo Android onde não é possível, individualmente, definirmos fontes diferenciadas pela nova API, Fonts in XML.

A primeira parte deste conteúdo você tem no seguinte artigo: Fontes em XML, Android O. Configuração e Uso. Não deixe de acessá-lo caso este seja seu primeiro contato com a nova API de família de fontes do Android.

Nesta parte vamos definir fontes individuais para itens e subitens do menu gaveta. Fontes para Toast info e também para Snackbar info.

Mas note que o objetivo é mostrar que quando as definições em XML não funcionarem, muito provavelmente o melhor caminho é via programação.

A seguir os tópicos abordados:

Estratégia: começando pelo fácil

Vamos utilizar a estratégia Facebook de desenvolvimento de projeto: das coisas que têm prioridade, desenvolver primeiro o fácil.

A seguir os itens que devem ter suas fontes atualizadas:

  • Itens NavigationView - atividade principal;
  • Snackbar - atividade principal;
  • Toast - atividade de detalhes.

Em ordem de fácil para difícil, temos:

  • Snackbar - atividade principal;
  • Toast - atividade de detalhes;
  • Itens NavigationView - atividade principal.

Em todos utilizaremos a família de fontes sans-serif, isso, pois assim a leitura dos textos e itens fica facilitada nesses trechos do aplicativo. Somente no título de grupo de subitens do NavigationView é que manteremos a fonte pokemon_solid.

Atualização Snackbar

Atualmente temos o Snackbar com a fonte pokemon_hollow:

Quase que impossível de se ler algo.

Para colocarmos a fonte sans-serif disponível pelo sistema, vamos criar um novo método na atividade principal onde haverá a atualização de fonte e também a apresentação da mensagem.

Em PokemonsActivity adicione o seguinte método:

...
fun customSnackbar( view: View ) {
val snackBar = Snackbar
.make( view,"Ainda falta implementar uma ação", Snackbar.LENGTH_LONG)
.setAction("Ação", null)

val tv = snackBar
.view
.findViewById<TextView>( android.support.design.R.id.snackbar_text )

tv.typeface = Typeface.create("sans-serif", Typeface.NORMAL)

snackBar.show()
}
...

 

Note que a invocação do Snackbar é exatamente a mesma utilizada no onCreate() da atividade, somente retardamos a invocação do método show() para primeiro acessarmos o TextView interno ao snack e em seguida atualizar a família de fontes deste componente visual.

Note que desta vez utilizamos o Typeface.create() ao invés do ResourcesCompat.font(), isso, pois estamos agora acessando uma família de fontes interna ao sistema.

Para finalizar a atualizacão da fonte do Snackbar, no onCreate() remova o código interno a fab.setOnClickListener() e coloque a invocação do novo método customSnackbar():

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

fab.setOnClickListener {
customSnackbar( it )
}
...
}
...

 

Executando o aplicativo e acionando o FloatingActionButton, temos:

Atualização Toast

Diferente do Snackbar, onde sabemos que há um TextView e o ID de acesso a ele, com o Toast temos que:

  • Criar um layout personalizado;
  • Inflar este layout;
  • Obter o TextView;
  • Alterar a família de fontes dele;
  • Vincular o layout ao Toast;
  • Apresentar o conteúdo invocando o método show().

Parece complicado, mas não. O layout é simples e o processo de infla-lo e vincula-lo ao Toast também.

Nosso Toast está na atividade de detalhes do aplicativo e não está nada fácil ler a informação presente nele:

Definição do layout personalizado

Antes de criar o layout, veja na imagem anterior que as bordas do container da mensagem são arredondadas e ainda, até o momento da construção deste artigo, não conseguimos fazer isso de maneira trivial no Android, digo, temos de criar um XML drawable somente para isso.

Assim vamos iniciar por ele. Em /res/drawable crie o XML corner_border.xml como a seguir:

<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<solid
android:color="#D666" />

<corners
android:radius="20dp"/>
</shape>

 

O atributo shape é para definirmos o formato da figura que deverá ser desenhada. A tag <solid> é para definirmos a cor de background. A tag <corners> é para definirmos o arco de arredondamento de cada ponta da figura.

Agora o XML do layout, /res/layout/toast_custom.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/corner_border"
android:orientation="horizontal"
android:padding="20dp">

<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#fff" />
</LinearLayout>

 

Com as definições acima, incluindo o background em drawable, conseguimos o mesmo box padrão de apresentação de informação via Toast API.

Código Toast de layout e família de fontes personalizados

Em DetalhesActivity crie o método que será invocado pelo FloatingActionButton, o método customToast():

...
fun customToast(){
val llToast = layoutInflater.inflate(
R.layout.toast_custom,
findViewById<ViewGroup>(R.id.ll_toast) )

val tvInfo = llToast.findViewById<TextView>(R.id.tv_info)
tvInfo.text = "Ainda falta implementar esta parte"
tvInfo.typeface = Typeface.create(
"sans-serif",
Typeface.NORMAL)

val toast = Toast( applicationContext )
toast.duration = Toast.LENGTH_SHORT
toast.view = llToast
toast.show()
}
...

 

Fique tranquilo quanto a novas variáveis de instância, pois nenhuma foi adicionada. Com o Kotlin nós facilmente temos acesso ao objeto de inflar layouts pela propriedade layoutInflater e também ao objeto de contexto de aplicação, applicationContext.

Como conteúdo do FAB no onCreate() da atividade de detalhes, coloque:

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

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

fab.setOnClickListener {
customToast()
}
...
}
...

 

Executando o aplicativo, abrindo um Pokémon qualquer e então acionando o FloatingActionButton, temos: 

Atualização dos itens do menu gaveta

Primeiro, saiba que o como o Toolbar, o NavigationView permite o uso de um atributo TextAppearance, mais precisamente o atributo itemTextAppearance.

Porém em nosso caso queremos todos os itens com uma fonte sans-serif do sistema e os títulos de subitens com a fonte pokemon_solid.

A seguir o que temos até o momento é:

Quase ilegível, certo?

A fonte pokemon_hollow está sendo utilizada, isso, pois como queríamos ela nos itens de menu de topo, o que fizemos na parte um desta série de dois artigos, tivemos de defini-la no tema do aplicativo.

Tendo em mente que até o momento da construção deste post não era possível modificar a família de fontes de itens de menu de topo por outro caminho.

Utilizando o estilo fontePokemon em itemTextAppearance no NavigationView do layout /res/layout/activity_pokemons.xml, estilo que faz uso de pokemon_solid, temos em código:

...
<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:itemTextAppearance="@style/fontePokemon"
app:itemTextColor="#666"
app:menu="@menu/activity_pokemons_drawer" />
...

 

Logo depois, executando o aplicativo, temos:

Ou seja: ainda nada próximo do que queremos. Vamos as atualizações em código, utilizando SpannableString.

Subclasse de TypefaceSpan para definição de família de fontes

No projeto Android que estamos desenvolvendo nesta série de dois artigos, crie um novo pacote, /util, e nele uma nova classe, CustomTypefaceSpan:

class CustomTypefaceSpan(typeFace: Typeface) : TypefaceSpan("") {

val newTypeFace = typeFace

override fun updateDrawState(paint: TextPaint) {
applyCustomTypeFace(paint, newTypeFace)
}

override fun updateMeasureState(paint: TextPaint) {
applyCustomTypeFace(paint, newTypeFace)
}

private fun applyCustomTypeFace(paint: Paint, typeface: Typeface) {
val styleAnterior: Int
val typefaceAnterior = paint.getTypeface()

if (typefaceAnterior == null) {
styleAnterior = 0
}
else {
styleAnterior = typefaceAnterior.getStyle()
}

/* PARA VERIFICAR A COMPATIBILIDADE DE ESTILOS */
val fake = styleAnterior and typeface.style.inv()

/*
* VERIFICA SE A FONTE MAIS ATUAL JÁ ESTÁ DE ACORDO
* COM A ANTERIOR EM TERMOS DE "TEXTO EM NEGRITO",
* CASO NÃO, ATUALIZA.
* */
if (fake and Typeface.BOLD != 0) {
paint.setFakeBoldText(true)
}

/*
* VERIFICA SE A FONTE MAIS ATUAL JÁ ESTÁ DE ACORDO
* COM A ANTERIOR EM TERMOS DE "TEXTO EM ITÁLICO",
* CASO NÃO, ATUALIZA.
* */
if (fake and Typeface.ITALIC != 0) {
paint.setTextSkewX(-0.25f)
}

/* APLICA A FONTE */
paint.setTypeface(typeface)
}
}

 

O código de CustomTypefaceSpan é um código boilerplate, pois independente do domínio do problema, caso a única solução de atualização de família de fontes de uma determinada área de seu projeto seja via TypefaceSpan, o código será exatamente o mesmo.

Atualização de fonte de itens e subitens

Na atividade principal do projeto, PokemonsActivity, vamos criar um método que receberá dois argumentos: o item que deverá ter a família de fonte atualizada; e o objeto Typeface desta fonte.

Segue código do novo método setCustomFontMenuItem():

...
private fun setCustomFontMenuItem (menuItem: MenuItem, typeface: Typeface ) {
val textItem = SpannableString( menuItem.title )
textItem.setSpan(
CustomTypefaceSpan( typeface ),
0,
textItem.length,
Spannable.SPAN_INCLUSIVE_INCLUSIVE )

menuItem.title = textItem
}
...

 

Agora temos de ter acesso a cada item e subitem do menu gaveta. Para isso, quando for um item com subitens, vamos utilizar a fonte pokemon_solid.

Segue código do novo método customFontNavigationViewMenu():

...
private fun customFontNavigationViewMenu() {
val pokemonSolid = ResourcesCompat.getFont( this, R.font.pokemon_solid )
val sansSerif = Typeface.create( "sans-serif", Typeface.NORMAL )
val menu = nav_view.menu

for( i in 0..menu.size() - 1 ){
val item = menu.getItem(i)
setCustomFontMenuItem( item, sansSerif )

if( item.subMenu != null ){ /* É UM ITEM COM SUBITENS? */
setCustomFontMenuItem( item, pokemonSolid ?: sansSerif )
val subMenu = item.subMenu

for( j in 0..subMenu.size() - 1 ){
val subItem = subMenu.getItem( j )
setCustomFontMenuItem( subItem, sansSerif )
}
}
}
}
...

 

Não tente utilizar o loop iterador com o objeto menu, pois este não implementa um Iterator.

Agora somente temos de colocar a invocação de customFontNavigationViewMenu() no método onCreate():

...
override fun onCreate(savedInstanceState: Bundle?) {
...
customFontNavigationViewMenu()
}
...

 

Executando o aplicativo, temos:

Se está pensando: seria melhor ter utilizado um framework de lista no lugar do NavigationView, assim, no adapter, facilmente atualizaríamos a família de fontes dos itens e subitens.

Na verdade este pensamento é errado caso parte da meta fosse um algoritmo menor. Você teria muito mais código do que somente criar uma subclasse de TypefaceSpam e logo depois acessar cada item e subitem do menu gaveta.

Com isso finalizamos a série de dois artigos sobre atualização de família de fontes em aplicativos Android.

Não esqueça de se inscrever na lista de emails do Blog, logo ao lado, para receber os conteúdos exclusivos, em primeira mão, sobre o dev Android.

Se inscreva também no canal: Thiengo YouTube.

Slides

A seguir os slides com a apresentação do projeto Android, Pokédex, junto ao uso das APIs Fonts in XML e Typeface:

Vídeo

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

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

Conclusão

Como informado nos pontos negativos do primeiro conteúdo desta série de fontes customizadas: ainda não temos uma maneira trivial de definirmos fontes individuais para todos os componentes visuais do Android.

É preciso adotar a estratégia certa de acordo com quais lugares deverão receber atualização de fonte. Como fizemos aqui: escolhemos como fonte padrão a fonte que queríamos no item de menu de topo, onde não conseguimos atualizar de outra maneira.

Um outro ponto que vimos no conteúdo deste artigo é a necessidade da criação de um arquivo XML de <shape> somente para colocarmos bordas arredondadas em um componente visual.

Algo que devemos esperar por melhoria nas próximas versões do Android, até porque já faz um bom tempo que a aplicação de bordas arredondas é desta maneira.

Não esqueça de se inscrever no Blog, na lista de emails, para receber os conteúdos exclusivos e em primeira mão.

Deixe abaixo seu comentário: o que achou, sua dúvida ou então sua sugestão.

Abraço.

Fontes

Fonts in XML

Recursos desenháveis: Desenhável de formato

Documentação Android: TypefaceSpan

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

Relacionado

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
Fontes em XML, Android O. Configuração e UsoFontes em XML, Android O. Configuração e UsoAndroid

Compartilhar

Comentários Facebook

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...