Definindo Fontes em Trechos Não Triviais do Android
(2796) (2)
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
CategoriaDesenvolvimento Web
Autor(es)Robert C. Martin
EditoraAlta Books
Edição1ª
Ano2023
Páginas416
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.
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;
- Atualização Snackbar;
- Atualização Toast:
- Atualização dos itens do menu gaveta:
- Slides;
- Vídeo;
- Conclusão;
- Fontes.
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 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:
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 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
Recursos desenháveis: Desenhável de formato
Documentação Android: TypefaceSpan
Comentários Facebook