Ajuste de Texto com Autosizing TextView - Android Jetpack

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 /Ajuste de Texto com Autosizing TextView - Android Jetpack

Ajuste de Texto com Autosizing TextView - Android Jetpack

Vinícius Thiengo
(861) (136)
Go-ahead
"Concentre todos seus pensamentos no trabalho que tem em mãos. Os raios solares não queimam até que sejam colocados em foco."
Alexander Graham Bell
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
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áginas936
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áginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Tudo bem?

Neste artigo vamos ao estudo completo de uma das novas APIs adicionadas ao Android junto ao release do Android 8, Oreo. Autosizing TextView, para expansão e contração de conteúdo em texto de forma dinâmica.

Vamos trabalhar direto com a API de suporte que diferente da API normal, dá suporte a partir do Android 4.0, Ice Cream Sandwich.

Animação com a Android Autosizing API em uso

Antes de prosseguir, saiba que aqui o termo "API" será utilizado como sinônimo de "biblioteca" (library).

Não esqueça de se inscrever 📩 na lista de emails do Blog para ter acesso aos conteúdos exclusivos.

A seguir os tópicos que estaremos abodando:

O que é e por que a Autosizing API?

A Autosizing TextView API é um dos componentes de experiência do usuário do pacote Jetpack, alias, junto ao Android Emoji é uma das APIs mais simples.

Segundo a documentação oficial, a Autosizing veio principalmente com o objetivo de manter o bom visual do texto do aplicativo quando há mudança de tipo de aparelho: de smartphone para tablet, por exemplo.

A regra da Autosizing API é:

Expandir ou contrair o texto (tamanho de fonte) o quanto possível sem que haja corte de conteúdo.

O corte de conteúdo é possível em um caso especifico, quando o tamanho mínimo definido para o texto faz com que ele ainda seja grande o suficiente para ficar enquadrado por completo no espaço do TextView.

Thiengo, então a Autosizing API somente é funcional no componente TextView?

Não. Em subclasses também. Um outro componente visual popular é o EditText.

Antes de partirmos aos códigos, ressalto que em meus testes encontrei alguns outros domínios em que a Autosizing é útil, sem que seja necessária a mudança de aparelho. Exemplo: domínios de problemas onde o título do conteúdo é importante em destaque, esses domínios também se beneficiarão da API em estudo.

Instalação da API

Para que seja possível o uso da versão de suporte da Autosizing API, é necessária a referência, direta ou indireta, a support-v4 26.0 ou a alguma versão superior dela, isso no Gradle App Level, ou build.gradle (Module: app):

...
dependencies {
implementation 'com.android.support:support-v4:28.0.0'
}

Configuração padrão de Autosizing

Primeiro é importante saber que no TextView (ou em suas subclasses em uso) não é recomendada a utilização do valor wrap_content em android:layout_height ou em android:layout_width, isso pois os resultados de enquadramento adequado ficam inconsistentes.

Assim, veja o layout a seguir, note o uso do atributo app:autoSizeTextType no primeiro TextView:

<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="16dp">

<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="#E0E0E0"
android:padding="8dp"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry."
app:autoSizeTextType="uniform" />

<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:background="#F0F0F0"
android:padding="8dp"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry." />
</LinearLayout>

 

O namespace app: está sendo utilizado, pois junto a API de suporte essa é a configuração correta. Sem a API de suporte, a parti do Android 8, utilizaríamos android:.

Os valores possíveis em autoSizeTextType são: uniform (ativação do autosizing); e none (não ativação).

Executando o projeto Android com o código de layout anterior, temos:

Configuração padrão de Autosizing TextView

Também é possível o acionamento do AutoSizeTextType via código dinâmico, Kotlin ou Java:

...
TextViewCompat
.setAutoSizeTextTypeWithDefaults(
tv_content, /* TextView. */
TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM /* Configuração. */
)
/*
* O outro possível valor como segundo argumento é
* TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM.
* */
...

Trabalhando a granularidade

A granularidade é a definição do:

  • tamanho mínimo - autoSizeMinTextSize;
  • tamanho máximo - autoSizeMaxTextSize;
  • e valor de mudança do tamanho atual para o próximo tamanho - autoSizeStepGranularity.

Entre os tamanhos mínimo e máximo definidos há inúmeros outros tamanhos possíveis de acordo com o valor colocado para grau de mudança, ou step de granularidade.

Utilizando o primeiro TextView do layout da seção anterior, junto aos atributos de granularidade, temos:

...
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="#E0E0E0"
android:padding="8dp"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry."
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="14sp"
app:autoSizeMaxTextSize="20sp"
app:autoSizeStepGranularity="2sp" />
...

 

Note que para ter efeito de autosizing o autoSizeTextType="uniform" ainda precisa ser definido. Executando o projeto com a nova configuração no primeiro TextView, temos:

Trabalhando a granularidade na Autosizing API

Na configuração atual tem que:

  • O tamanho mínimo possível em contração é de 14sp;
  • O tamanho máximo possível em expansão é de 20sp;
  • Devido ao step de 2sp os tamanhos intermediários possíveis são 16sp e 18sp.

A definição em código dinâmico seria:

...
TextViewCompat
.setAutoSizeTextTypeUniformWithConfiguration(
tv_content, /* TextView. */
14, /* Valor mínimo. */
20, /* Valor máximo. */
2, /* Valor de step. */
TypedValue.COMPLEX_UNIT_SP /* Tipo unidade, prefira SP. */
)
...

 

A granularidade padrão, quando não definida de maneira explícita, é:

  • Tamanho mínimo = 12sp;
  • Tamanho máximo = 112sp;
  • Step = 1px (é isso mesmo, 1 pixel).

Tamanhos pré-definidos

É possível, com um array e o atributo autoSizePresetSizes definirmos os valores cabíveis na expansão ou contração do texto. O local comum para conter esses valores é o /res/values/arrays.xml:

<resources>
<array name="autosize_text_sizes">
<item>30sp</item>
<item>32sp</item>
<item>34sp</item>
<item>36sp</item>
<item>58sp</item>
</array>
</resources>

 

E então, utilizando o primeiro TextView do layout de testes, temos:

...
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="#E0E0E0"
android:padding="8dp"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry."
app:autoSizeTextType="uniform"
app:autoSizePresetSizes="@array/autosize_text_sizes" />
...

 

Note que como acontece com a definição de granularidade, com PresetSizes também temos de utilizar autoSizeTextType="uniform". Executando o projeto com a nova configuração do TextView, temos:

Trabalhando o autoSizePresetSizes na Autosizing API

A definição de PresetSizes em código dinâmico pede primeiro que tenhamos um array de inteiros (sem o sufixo sp):

...
<array name="autosize_text_sizes_int">
<item>30</item>
<item>32</item>
<item>34</item>
<item>36</item>
<item>58</item>
</array>
...

 

Depois o código:

val arraySizes = resources.getIntArray( R.array.autosize_text_sizes_int )

TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes(
tv_content, /* TextView. */
arraySizes, /* Array de inteiros com os tamanhos pré-definidos. */
TypedValue.COMPLEX_UNIT_SP /* Tipo unidade, prefira SP. */
)

 

Aqui foi preferível colocar os inteiros em um XML de array, mas você pode defini-lo também em seu código dinâmico.

Considerações finais

Felizmente a Autosizing é uma API simples de entender e de utilizar, com ela, mesmo que somente em domínios de problemas específicos, não necessitamos mais de apoio de APIs de terceiro para o correto enquadramento do texto.

A Autosizing API faz parte do pacote de APIs e frameworks Android Jetpack, pacote que contém todas as tecnologias consideradas, pelo Android, modernas para o desenvolvimento eficiente de aplicativos.

Se você trabalha com títulos em destaque, títulos de itens e produtos, por exemplo, pode ser que a Autosizing TextView seja um bom caminho para melhorar o seu app Android.

Um ponto importante a ser abordado é que as definições de estilo e de família de fonte personalizada no componente visual de texto, essas definições não removem a funcionalidade de Autosizing, algo que ocorria logo no lançamento da API.

Projeto Android

Para projeto de exemplo vamos construir a tela de endereço de um app de salão de beleza.

Nessa área haverá alguns dados que terão de ser destacados para facilitar a conversão de um visitante do aplicativo em um cliente. Assim utilizaremos a Autosizing API para facilitar o destaque.

O projeto será desenvolvido em duas pequenas partes:

  • Na primeira parte vamos ao desenvolvimento integral da tela de endereço;
  • Na segunda parte vamos adicionar a melhoria aos TextViews alvos.

O projeto de exemplo está presente no seguinte GitHub: https://github.com/viniciusthiengo/sonia-salao-de-beleza-kotlin-android.

Como informado também em outros projetos aqui do Blog: acompanhe o exemplo até o final, pois é possível assim melhor visualizar a uso eficiente da API em estudo.

Protótipo estático

A seguir as telas iniciais do protótipo estático do projeto:

Tela de abertura

Tela de abertura

Menu gaveta aberto

Menu gaveta aberto

Tela de endereço e contato com o salão

Tela de endereço e contato com o salão

 


Iniciando o projeto

Em seu Android Studio inicie um novo projeto Kotlin:

  • Nome da aplicação: Sônia Salão de Beleza;
  • API mínima: 16 (Android Jelly Bean);
  • Atividade inicial: Navigation Drawer Activity;
  • Nome da atividade inicial: AddressActivity. O nome do layout da atividade inicial será atualizado automaticamente;
  • Para todos os outros campos, deixe-os com os valores já definidos por padrão.

Ao final do projeto teremos a seguinte arquitetura:

Arquitetura Android Studio do projeto de salão

Configurações Gradle

A seguir as configurações do Gradle Project Level, ou build.gradle (Project: SniaSalodeBeleza):

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

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

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

 

Então a configuração do Gradle App Level, ou build.gradle (Module: app):

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

android {
compileSdkVersion 28
defaultConfig {
applicationId "thiengo.com.br.sniasalodebeleza"
minSdkVersion 16
targetSdkVersion 28
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 fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
}

 

Como a referência ao pacote de design, design:28.0.0, está em uma versão acima da 26.0 não precisaremos posteriormente definir explicitamente o pacote support-v4, pois este já é referenciado internamente pela design:28.0.0.

Configurações AndroidManifest

A seguir as configurações do AndroidManifest.xml:

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

<application
android:hardwareAccelerated="true"
android:allowBackup="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=".AddressActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">

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

</application>
</manifest>

Configurações de estilo

Para os arquivos de estilo vamos iniciar com o arquivo de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#E91E63</color>
<color name="colorPrimaryDark">#C2185B</color>
<color name="colorAccent">#7AD15A</color>

<color name="colorNavigationView">#F5F5F6</color>
<color name="colorItemNormal">#777777</color>

<color name="colorDarkTextLabel">#222222</color>
<color name="colorPurpleTextLabel">#AA1B52</color>
<color name="colorGreyIcon">#969FAA</color>
</resources>

 

Então o arquivo de dimensões: /res/values/dimens.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="content_margin_top">14dp</dimen>
<dimen name="text_size">16sp</dimen>
</resources>

 

Agora o arquivo de Strings, /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sônia Salão de Beleza</string>

<string name="navigation_drawer_open">Abrir menu gaveta</string>
<string name="navigation_drawer_close">Fechar menu gaveta</string>

<string name="label_services">Serviços</string>
<string name="label_bride_day">Dia de noiva</string>
<string name="label_packages">Pacotes</string>
<string name="label_schedule">Agendamento</string>
<string name="label_address">Endereço (rota em mapa)</string>

<string name="whatsapp_needed_info">
Instale o WhatsApp em seu aparelho.
</string>

<string name="label_located_in">Estamos localizados em:</string>
<string name="desc_icon_address">Ícone de endereço</string>
<string name="address">
Av. Américo Buaiz, nº 200, Serra - Espírito Santo. 2º andar,
loja 352 - ao lado da Los Neto.
</string>
<string name="desc_background_address">
Imagem de background da área de endereço.
</string>

<string name="label_are_you_in_mall">
Chegou no shopping? Pode nos acionar pelo WhatsApp se quiser:
</string>
<string name="desc_icon_whatsapp">Ícone do WhatsApp</string>
<string name="whatsapp_number">
+55 (27) 9&#8211;9988&#8211;7766
</string>

<string name="label_difficulty_finding_us">
Está tendo dificuldades em nos encontrar? Acione o botão
abaixo para que a rota seja apresentada a você.
</string>
<string name="label_view_route">Visualizar rota</string>

<string name="apps_needed_info">
Instale o Google Maps ou algum navegador para poder visualizar
a rota.
</string>
</resources>

 

Assim os arquivos de definição de tema. Primeiro o específico para versões do Android a partir da API 21, Lollipop, /res/values-v21/styles.xml:

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

<!--
Para que a barra de topo padrão não seja utilizada e
assim somente o AppBarLayout junto ao Toolbar possam ser
usados. Somando a isso a aplicação de transparência na
statusBar.
-->
<style name="AppTheme.NoActionBar">

<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

 

Por fim o arquivo padrão de estilos, /res/values/styles.xml:

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

<!-- Estilo padrão, aplicado em todo o projeto. -->
<style
name="AppTheme"
parent="Theme.AppCompat.Light.DarkActionBar">

<item name="android:windowBackground">@drawable/background</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<!--
Para que a barra de topo padrão não seja utilizada e
assim somente o AppBarLayout junto ao Toolbar possam ser
usados.
-->
<style name="AppTheme.NoActionBar">

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

<!-- Para o correto enquadramento do AppBarLayout. -->
<style
name="AppTheme.AppBarOverlay"
parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

<!--
Utilizado para a correta apresentação de menus de pop-up
em barra de topo.
-->
<style
name="AppTheme.PopupOverlay"
parent="ThemeOverlay.AppCompat.Light" />
</resources>

Atividade principal, endereço e contato

Vamos iniciar com os arquivos de layout da AddressActivity. Primeiro o arquivo de conteúdo, /res/layout/content_address.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"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".AddressActivity"
tools:showIn="@layout/app_bar_address">

<LinearLayout
android:padding="16dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:textColor="@color/colorPurpleTextLabel"
android:id="@+id/tv_we_are_located_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_located_in"
android:textSize="@dimen/text_size"/>

<RelativeLayout
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_marginBottom="26dp"
android:layout_height="0dp">

<ImageView
android:id="@+id/iv_ic_address"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginStart="26dp"
android:layout_marginLeft="26dp"
android:layout_marginTop="@dimen/content_margin_top"
android:contentDescription="@string/desc_icon_address"
android:src="@drawable/ic_address"
android:tint="@color/colorGreyIcon"/>

<TextView
android:id="@+id/tv_address"
android:layout_width="match_parent"
android:onClick="showRoute"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_ic_address"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:textColor="@color/colorLinkText"
android:layout_toEndOf="@+id/iv_ic_address"
android:layout_toRightOf="@+id/iv_ic_address"
android:text="@string/address"
android:textSize="@dimen/text_size"/>
</RelativeLayout>

<TextView
android:id="@+id/tv_whatsapp_contact_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_are_you_in_mall"
android:textColor="@color/colorPurpleTextLabel"
android:textSize="@dimen/text_size"/>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">

<ImageView
android:id="@+id/iv_ic_whatsapp"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginStart="26dp"
android:layout_marginLeft="26dp"
android:layout_marginTop="@dimen/content_margin_top"
android:contentDescription="@string/desc_icon_whatsapp"
android:onClick="whatsAppHelp"
android:src="@drawable/ic_whatsapp"
android:tint="@color/colorGreyIcon"/>

<TextView
android:id="@+id/tv_whatsapp_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_ic_whatsapp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_toEndOf="@+id/iv_ic_whatsapp"
android:layout_toRightOf="@+id/iv_ic_whatsapp"
android:onClick="whatsAppHelp"
android:textColor="@color/colorLinkText"
android:text="@string/whatsapp_number"
android:textSize="@dimen/text_size"/>
</RelativeLayout>

<TextView
android:id="@+id/tv_difficulty_finding_us"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_difficulty_finding_us"
android:textColor="@color/colorPurpleTextLabel"
android:textSize="@dimen/text_size"/>

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_margin_top"
android:background="@color/colorAccent"
android:onClick="showRoute"
android:text="@string/label_view_route"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

 

A seguir o diagrama do layout anterior:

Diagrama do layout content_address.xml

Agora o XML do arquivo de layout que contém o layout de conteúdo, /res/layout/app_bar_address.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"
tools:context=".AddressActivity">

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

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

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

 

Abaixo o diagrama do layout app_bar_address.xml:

Diagrama do layout app_bar_address.xml

Agora o arquivo principal de layout, /res/layout/activity_address.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:background="@android:color/white"
android:fitsSystemWindows="true"
tools:openDrawer="start">

<include
layout="@layout/app_bar_address"
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="@color/colorNavigationView"
android:fitsSystemWindows="true"
app:itemBackground="@drawable/nav_item_background"
app:itemIconTint="@color/nav_icon_text"
app:itemTextColor="@color/nav_icon_text"
app:menu="@menu/activity_address_drawer"/>

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

 

A seguir os arquivos de configuração de itens e de background de menu gaveta. Primeiro o arquivo de definição de cor de ícone e de texto dos itens de menu gaveta, /res/color/nav_icon_text.xml:

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

<!--
A ordem dos itens de um arquivo <selector> deve ser
seguida de forma estrita, pois caso contrário os efeitos
esperados não ocorrerão.
-->

<!-- Estado "Selecionado" -->
<item
android:color="@android:color/white"
android:state_checked="true" />

<!-- Estado "Normal", não selecionado -->
<item android:color="@color/colorItemNormal" />
</selector>

 

Agora o arquivo de definição de background de item de menu gaveta, /res/drawable/nav_item_background.xml:

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

<!-- Estado "Selecionado" -->
<item
android:drawable="@color/colorAccent"
android:state_checked="true" />

<!-- Estado "Normal", não selecionado -->
<item android:drawable="@android:color/transparent" />
</selector>

 

Assim o XML de itens vinculado ao NavigationView do menu gaveta, segue /res/menu/activity_address_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/nav_service"
android:icon="@drawable/ic_services"
android:title="@string/label_services" />
<item
android:id="@+id/nav_bride_day"
android:icon="@drawable/ic_bride_day"
android:title="@string/label_bride_day" />
<item
android:id="@+id/nav_packages"
android:icon="@drawable/ic_packages"
android:title="@string/label_packages" />
<item
android:id="@+id/nav_schedule"
android:icon="@drawable/ic_schedule"
android:title="@string/label_schedule" />
<item
android:id="@+id/nav_address"
android:checked="true"
android:icon="@drawable/ic_address"
android:title="@string/label_address" />
</group>
</menu>

 

Segue diagrama do menu anterior:

Diagrama do menu activity_address_drawer.xml

Então o diagrama do layout activity_address.xml:

Diagrama do layout activity_address.xml

Por fim o código Kotlin da AddressActivity:

class AddressActivity :
AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener {

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

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

nav_view.setNavigationItemSelectedListener( this )
}

override fun onResume() {
super.onResume()
/*
* Hackcode para que seja possível atualizar o título
* da barra de topo sem que seja necessário mudar o
* nome do aplicativo.
* */
toolbar.title = getString(R.string.label_address)
}

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

override fun onNavigationItemSelected( item: MenuItem ): Boolean {
/*
* Foi deixado aqui dentro somente o necessário para
* fechar o menu gaveta quando algum item for acionado.
* */
drawer_layout.closeDrawer( GravityCompat.START )
return false /* Para não mudar o item selecionado em menu gaveta */
}

/*
* Método ouvidor para permitir que o usuário entre em contato
* com o WhatsApp correto com apenas um acionamento em tela.
* */
fun whatsAppHelp( view: View){
/* O número abaixo é fictício. */
val whatsAppUri = Uri.parse( "smsto:27999887766" )
val intent = Intent( Intent.ACTION_SENDTO, whatsAppUri )

intent.setPackage( "com.whatsapp" )

/*
* Garantindo que a Intent somente será acionada se o
* aplicativo WhatsApp estiver presente no aparelho.
* */
if( intent.resolveActivity( packageManager ) != null ){
startActivity( intent )
}
else{
Toast
.makeText(
this,
getString(R.string.whatsapp_needed_info),
Toast.LENGTH_SHORT
)
.show()
}
}

/*
* Método listener de toque (clique) no botão "VISUALIZAR ROTA",
* responsável por invocar o Google Maps para apresentar ao
* usuário a rota que ele terá de percorrer até o salão de
* beleza, isso partindo do ponto atual dele. Como o salão de
* beleza é fictício, está sendo utilizada uma estética presente
* em Morada de Laranjeiras, Serra, ES.
* */
fun showRoute(view: View){
var beautySalon = "Rebecca Miranda Centro Estético, " +
"Morada de Laranjeiras, Serra, Espírito Santo, Brasil"
beautySalon = Uri.encode( beautySalon )

var navigation = "google.navigation:q=$beautySalon"

var navigationUri = Uri.parse( navigation )
var intent = Intent( Intent.ACTION_VIEW, navigationUri )

intent.setPackage( "com.google.android.apps.maps" )

/*
* Caso o aplicativo do Google Maps não esteja presente no
* aparelho (algo difícil de acontecer), partimos para a
* apresentação de rota pelo Google Maps Web, via navegador
* mobile.
* */
if( intent.resolveActivity( packageManager ) == null ){

val dirAction = "dir_action=navigate"
val destination = "destination=$beautySalon"
navigation = "https://www.google.com/maps/dir/?api=1&$dirAction&$destination"

navigationUri = Uri.parse( navigation )
intent = Intent( Intent.ACTION_VIEW, navigationUri )
}

if( intent.resolveActivity( packageManager ) != null ){

startActivity( intent )
}
else{
/*
* Se nem Google Maps e nem navegador mobile estiverem
* presentes no aparelho, informe ao usuário para
* instalar ao menos um dos dois.
* */
Toast
.makeText(
this,
getString( R.string.apps_needed_info ),
Toast.LENGTH_LONG
)
.show()
}
}
}

 

Com isso podemos partir para a melhoria do aplicativo.

Atualização de projeto, melhorando o destaque da área de endereço

No estado atual da área de endereço do aplicativo de salão, além da apresentação do endereço tem a opção de WhatsApp (aberto quando há o toque nele) e a opção de abrir a rota do local atual do usuário até o salão:

Tela antiga de endereço e contato do app Android de salão

Mas por incrível que pareça, há usuários que não acionarão o botão de rota ou o link do WhatsApp devido ao pouco destaque.

Assim, nossa meta na melhoria do destaque, para um provável aumento na conversão de visitantes a clientes, será:

  • Destacar a cor dos textos acionáveis, incluindo o algoritmo de acionamento de rota via texto de endereço e não somente pelo botão de rota;
  • Permitir a expansão dos textos de endereço e de número de WhatsApp de acordo com o espaço livre a eles.

Nova tela de endereço

A nova tela de endereço deverá ser como a do protótipo estático a seguir:

Tela de endereço com Autosizing

Tela de endereço com Autosizing

Atualizando a cor dos textos links

Primeiro, no arquivo /res/values/colors.xml, adicione a cor em destaque:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<color name="colorLinkText">#1B9CE2</color>
</resources>

 

Assim a atualização do TextView de endereço em content_address.xml:

...
<TextView
android:id="@+id/tv_address"
...
android:textColor="@color/colorLinkText" />
...

 

E então, no mesmo arquivo XML, a atualização da cor do TextView de número do WhatsApp:

...
<TextView
android:id="@+id/tv_whatsapp_number"
...
android:textColor="@color/colorLinkText" />
...

Autosizing para o texto de endereço

Para o texto de endereço vamos definir o Autosizing com uma nova configuração de granularidade, isso para que o texto não ocupe todo o espaço em tela e assim remova os outros componentes da área de endereço.

No XML content_address.xml atualize o TextView de endereço como a seguir:

...
<TextView
android:id="@+id/tv_address"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@+id/iv_ic_address"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:layout_toEndOf="@+id/iv_ic_address"
android:layout_toRightOf="@+id/iv_ic_address"
android:text="@string/address"
android:textColor="@color/colorLinkText"
android:onClick="showRoute"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="28sp"
app:autoSizeStepGranularity="1sp" />
...

 

A adição de android:alignParentBottom="true" foi necessária, pois assim garantimos que android:layout_height="match_parent" junto a android:layout_width="match_parent" não faça o TextView sobrepor o ImageView de ícone ao lado dele.

O método showRoute() também já está vinculado ao listener de clique via android:onClick.

Autosizing para o número do WhatsApp

O número do WhatsApp do salão terá de ter um controle maior sobre o tamanho possível dele, isso, pois caso contrário ele crescerá a ponto de até mesmo remover da tela o TextView de endereço.

A melhor modificação seria a mudança dos componentes visuais root pelo ConstraintLayout, mas é possível ainda ter ganhos com a Autosizing API sem a necessidade de realizar essa atualização que seria complexa.

Aqui utilizaremos a configuração de PresetSizes, mais também pela didática, para ter em uso todos os componentes possíveis de configuração de autosizing.

No folder /res/values adicione o arquivo XML arrays.xml como a seguir:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="text_size_whatsapp_number">
<item>12sp</item>
<item>14sp</item>
<item>16sp</item>
<item>18sp</item>
<item>20sp</item>
</array>
</resources>

 

Ainda no arquivo XML content_address.xml atualize o TextView do número do WhatsApp como a seguir:

...
<TextView
android:id="@+id/tv_whatsapp_number"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@+id/iv_ic_whatsapp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_toEndOf="@+id/iv_ic_whatsapp"
android:layout_toRightOf="@+id/iv_ic_whatsapp"
android:onClick="whatsAppHelp"
android:text="@string/whatsapp_number"
android:textColor="@color/colorLinkText"
app:autoSizeTextType="uniform"
app:autoSizePresetSizes="@array/text_sizes_whatsapp_number" />
...

Testes e resultados

Com o Android Studio aberto vá em "Build", então em "Rebuid project". Ao final do rebuild execute o aplicativo em seu aparelho ou emulador Android de testes.

Com o aparelho na vertical, posição comum para os usuários do app de exemplo, temos:

Tela de endereço, na vertical, com Autosizing TextView

Com o aparelho na horizontal temos o ajuste, onde o destaque dos textos links fica mesmo por conta da cor deles, pois o tamanho é diminuído:

Tela de endereço, na horizontal, com Autosizing TextView

Assim terminamos o estudo deste simples, mas útil, componente de UI do Android Jetpack.

Não deixe de se inscrever na 📩 lista de emails do Blog, logo abaixo ou ao lado, para receber em primeira mão os conteúdos exclusivos sobre o desenvolvimento Android.

Se inscreva também no canal do Blog em: YouTube Thiengo.

Slides

Abaixo os slides com a apresentação completa da API Autosizing TextView:

Vídeos

A seguir os vídeos com o passo a passo de implementação da Autosizing TextView API no aplicativo de exemplo:

Para acessar o projeto de exemplo, entre no GitHub em: https://github.com/viniciusthiengo/sonia-salao-de-beleza-kotlin-android.

Conclusão

O trabalho com a API Autosizing TextView pode ser limitado em termos de domínio de problema, mas essa melhoria já temos adicionada de maneira nativa no Android, algo que tínhamos de buscar em APIs de terceiros até pouco tempo atrás.

Para aqueles que têm de ter uma alta consistência entre diferentes aparelhos, principalmente entre smartphones e tablets, a biblioteca de autosizing entra como uma luva.

De qualquer forma, o uso da Autosizing não dispensa os testes de consistência na UI do aplicativo Android em desenvolvimento. 

Assim finalizamos o conteúdo. Caso você tenha alguma dica ou dúvida sobre componentes de texto no Android, deixe logo abaixo nos comentários.

Se curtiu o conteúdo, não esqueça de compartilha-lo. E por fim, não deixe de se inscrever na 📩 lista de emails.

Abraço.

Fontes

Autosizing TextViews

Android Jetpack: Autosizing TextView

Auto-Sizing TextViews in Android

Autosizing of TextView doesn't work (Android O) - Resposta de Sira Lam

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

Relacionado

Lottie API Para Animações no AndroidLottie API Para Animações no AndroidAndroid
PhotoView Android Para a Completa Implementação de ZoomPhotoView Android Para a Completa Implementação de ZoomAndroid
Data Binding Para Vinculo de Dados na UI AndroidData Binding Para Vinculo de Dados na UI AndroidAndroid
Live Templates Para Otimização de Tempo no Android StudioLive Templates Para Otimização de Tempo no Android StudioAndroid

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