Definindo Fontes em Trechos Não Triviais do Android

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

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 Thiengo
(2796) (2)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloCraftsmanship Limpo: Disciplinas, Padrões e ética
CategoriaDesenvolvimento Web
Autor(es)Robert C. Martin
EditoraAlta Books
Edição
Ano2023
Páginas416
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

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.

Animação do aplicativo Android Pokédex

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

Antes de prosseguir, não deixe de se inscrever 📫 na lista de e-mails do Blog para receber, em primeira mão, todos os conteúdos de desenvolvimento Android, exclusivos aqui do Blog.

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:

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:

Snackbar com família de fontes errada

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 atualizaçã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:

SnackBar com a família de fontes correta, Roboto

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:

Toast com a família de fontes errada

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: 

Toast com a família de fontes correta, Roboto

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 é:

Menu gaveta com a família de fontes errada

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:

Menu gaveta com a família de fontes correta, Pokemon Solid

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:

Itens de menu gaveta com a família de fontes correta, Roboto

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 e-mails 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 e-mails, 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. E não esqueça de compartilhar.

Abraço.

Fontes

Fonts in XML

Recursos desenháveis: Desenhável de formato

Documentação Android: TypefaceSpan

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
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 (2)

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...
Alexandre Lobo (1) (0)
02/01/2018
Opa Thiengo, Gostaria de primeiramente agradecer pelos materiais gratuitos e tirar uma duvida, como eu faço para atualizar uma aplicação que estou desenvolvendo na faculdade onde comecei, o android studio e uma versão e no meu pc em casa e o mais atualizado. desde já obrigado
Responder
Vinícius Thiengo (0) (0)
03/01/2018
Alexandre, tudo bem?

Basta obter o arquivo (pasta) de seu projeto. Isso caso ele não tenha uma versão em algum repositório online (GitHub, BitBucket, ?), onde facilmente você poderia realizar o download dele.

Coloque a pasta em sua máquina e abra o projeto em sua versão de Android Studio, utilizando a opção "Open an existing Android Studio project". Os ajustes necessários serão aplicados automaticamente pelo IDE.

Note que caso o aplicativo já tenha sido assinado na máquina de sua faculdade, será necessário também copiar o arquivo .keystore gerado para o app no momento da assinatura, caso contrário uma nova versão dele, assinada em sua máquina, não terá validade para atualização de aplicativo na Google Play Store.

Também será necessário saber as senhas do arquivo .keystore, isso para conseguir manter a assinatura de nova versão do app utilizando o mesmo.

Abraço.
Responder