Início de Projeto e Menu Gaveta Customizado - Android M-Commerce
(8022) (39)
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
CategoriaEngenharia de Software
Autor(es)Kent Beck
EditoraNovatec
Edição1ª
Ano2024
Páginas112
Tudo bem?
Neste artigo vamos dar continuidade ao desenvolvimento do projeto Android de mobile-commerce, mais precisamente, ao aplicativo BlueShoes apresentado em Android Mobile-Commerce, Apresentação e Protótipo do Projeto.
Aqui iniciaremos com a codificação, sabendo que os protótipos e regras de negócio discutidos no artigo do link acima já nos dão o suficiente para seguir com o desenvolvimento.
Antes de prosseguir, não deixe de se inscrever 📩 na lista de emails do Blog para ter acesso aos conteúdos exclusivos sobre desenvolvimento Android e também aos conteúdos deste projeto de desenvolvimento.
A seguir os pontos abordados:
- Por onde iniciaremos a codificação?:
- Iniciando um novo projeto Android:
- Imagem de abertura de aplicativo:
- Imagem de fundo das atividades:
- Ícone de abertura de aplicativo:
- Definição do menu gaveta:
- Entendendo o funcionamento;
- Cabeçalho de menu para usuário não conectado;
- Cabeçalho de menu para usuário conectado;
- Listas de itens;
- Classe de domínio para itens de menu gaveta;
- Base de dados dos itens de menu;
- Ícones dos itens;
- Adapter dos itens;
- Lista principal, contendo categorias de calçados;
- Lista de itens para usuário conectado;
- E a seleção dos itens? Como será possível?;
- Instalação da API de seleção e atualizações de apoio;
- Classes de permissão a seleção de item;
- Atualização no adapter de itens;
- Classe de domínio, User;
- Inicialização dos SelectionTracker;
- E como será o acionamento de fragmentos?;
- Inicialização do menu gaveta.
- Ajustes extras:
- Testes e resultados;
- Vídeos;
- Conclusão;
- Fontes.
Por onde iniciaremos a codificação?
Como informado no primeiro artigo do projeto: primeiro desenvolveremos a parte de interface gráfica do aplicativo Android.
Porém é improvável que seja desenvolvida toda a parte gráfica em um único artigo. Sendo assim, vamos iniciar pelas partes essenciais e então vamos às partes simples para depois irmos aos trechos que exigirão maior codificação e empenho.
Neste artigo, vamos:
- Iniciar um novo projeto Android;
- Colocar os recursos necessários para início de projeto;
- Desenvolver toda a codificação da interface dos dois possíveis menus gaveta:
- Versão com usuário não conectado;
- Versão com usuários conectado.
- Remover o que não utilizaremos.
Por que o menu gaveta?
Porque este componente é a parte central do projeto, é a partir dele que poderemos navegar para as telas simples e telas complexas.
Para ficar mais claro o que será desenvolvido neste artigo, a seguir o protótipo das telas:
Abertura | Menu - usuário não conectado |
Menu - usuário conectado |
Note que nas telas de menu gaveta aberto somente trabalharemos o menu gaveta, a listagem de tênis ficará para outro artigo.
Iniciando um novo projeto Android
Em seu Android Studio inicie um novo projeto Kotlin:
- Atividade inicial: Navigation Drawer Activity;
- Nome da aplicação: BlueShoes;
- Linguagem: Kotlin;
- API mínima: 16 (Android Jelly Bean);
- Para todos os outros campos, mantenha os valores já definidos por padrão.
Com o fim das configurações de um novo projeto, teremos a seguinte arquitetura no Android Studio IDE:
Saiba que você pode acompanhar o projeto também pelo repositório dele no GitHub: https://github.com/viniciusthiengo/blueshoes-kotlin-android.
Arquivos de configuração (Gradle)
Para o Gradle Level de Projeto, build.gradle (Project: BlueShoes), temos:
buildscript {
ext.kotlin_version = '1.3.20'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Para o Gradle Level de Aplicativo, build.gradle (Module: app), temos:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "thiengo.com.br.blueshoes"
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.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'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
}
Note que as referências às APIs de testes foram todas removidas, pois não serão necessárias neste projeto.
Configurações AndroidManifest
A seguir as configurações iniciais do AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="thiengo.com.br.blueshoes">
<application
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=".view.MainActivity"
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
A seguir o arquivo de definição de cores, /res/values/colors.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#F9F9F9</color>
<color name="colorPrimaryDark">#C6C6C6</color>
<color name="colorAccent">#F5F5F5</color>
<color name="colorText">#666666</color>
<color name="colorLink">#00A6FF</color>
<color name="colorNavButton">#77D353</color>
<color name="colorViewLine">#DDDDDD</color>
<color name="colorNavItemSelected">#F1F1F1</color>
</resources>
Note que algumas configurações estáticas de projeto, configurações em XML, nós já estaremos definindo aqui, isso, pois é possível obtê-las de acordo com o que foi definido em protótipo estático. Para a escolha de cores foi utilizado o site oficial do Material Design IO.
Agora o arquivo de dimensões, /res/values/dimens.xml:
<resources>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>
Assim o arquivo de Strings, /res/values/strings.xml:
<resources>
<string name="app_name">BlueShoes</string>
<string name="navigation_drawer_open">Menu gaveta aberto</string>
<string name="navigation_drawer_close">Menu gaveta fechado</string>
<string name="nav_header_title">Android Studio</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="tx_login">Login</string>
<string name="tx_or">ou</string>
<string name="tx_me_sign_up">me Cadastrar</string>
<string name="tx_conected_as">Conectado como:</string>
<string name="item_all_shoes">Todos os calçados</string>
<string name="item_flip_flops">Chinelos</string>
<string name="item_cleats">Chuteiras</string>
<string name="item_sandals">Sandálias</string>
<string name="item_ballet_shoes">Sapatilhas</string>
<string name="item_suit_shoes">Sapatênis</string>
<string name="item_shoes">Tênis</string>
<string name="item_performance_shoes">Tênis performance</string>
<string name="item_contact">Contato BlueShoes</string>
<string name="item_about">Sobre</string>
<string name="item_privacy_policy">Políticas de privacidade</string>
<string name="item_my_orders">Meus pedidos</string>
<string name="item_settings">Minhas configurações</string>
<string name="item_sign_out">Sair</string>
</resources>
Agora o arquivo com algumas configurações de tema para o aplicativo quando em aparelhos Android com a API 21 ou superior, segue /res/values-v21/styles.xml:
<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>
E por fim o arquivo de definição geral de tema do aplicativo, /res/values/styles.xml:
<resources>
<!--
Estilo padrão, aplicado em todo o projeto.
-->
<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>
</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.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>
Note que em AppTheme.AppBarOverlay atualizamos o tema parent para ThemeOverlay.AppCompat.ActionBar ao invés do padrão ThemeOverlay.AppCompat.Dark.ActionBar que deixava os itens da barra de topo sem constraste com a cor de fundo dela.
Atividade principal
É importante mostrarmos também a codificação da única atividade do novo projeto com um menu gaveta, isso, pois ainda neste artigo, tanto o código dinâmico da atividade, código Kotlin, quanto o código estático, XML, passarão por atualizações.
Vamos iniciar com os códigos estáticos, códigos de layout. Primeiro o layout de conteúdo, /res/layout/content_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
A seguir o diagrama do layout anterior:
Agora o layout que contém o layout de conteúdo, /res/layout/app_bar_main.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=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
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_main"/>
<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="@android:drawable/ic_dialog_email"/>
</android.support.design.widget.CoordinatorLayout>
A seguir o diagrama do layout anterior:
Agora o layout de cabeçalho padrão do menu gaveta, /res/layout/nav_header.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: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"
android:orientation="vertical"
android:gravity="bottom">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_launcher_round"
android:contentDescription="@string/nav_header_desc"
android:id="@+id/imageView"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_header_subtitle"
android:id="@+id/textView"/>
</LinearLayout>
Abaixo o diagrama do layout anterior de cabeçalho:
Assim o XML de itens de menu, /res/menu/activity_main_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_camera"
android:icon="@drawable/ic_menu_camera"
android:title="Import"/>
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="Gallery"/>
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/ic_menu_slideshow"
android:title="Slideshow"/>
<item
android:id="@+id/nav_manage"
android:icon="@drawable/ic_menu_manage"
android:title="Tools"/>
</group>
<item android:title="Communicate">
<menu>
<item
android:id="@+id/nav_share"
android:icon="@drawable/ic_menu_share"
android:title="Share"/>
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_send"
android:title="Send"/>
</menu>
</item>
</menu>
A seguir o diagrama do menu anterior:
Então o layout principal da atividade, /res/layout/activity_main.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_main"
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:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/>
</android.support.v4.widget.DrawerLayout>
Abaixo o diagrama do principal layout da MainActivity:
Antes de partirmos para a codificação dinâmica da atividade principal, vamos ao simples XML de menu de topo, /res/menu/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
Abaixo o diagrama do layout anterior:
Assim o código dinâmico inicial da MainActivity:
class MainActivity :
AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener {
override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_main )
setSupportActionBar( toolbar )
fab.setOnClickListener { view ->
Snackbar
.make(
view,
"Mude para a sua própria 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()
nav_view.setNavigationItemSelectedListener( this )
}
override fun onBackPressed() {
if( drawer_layout.isDrawerOpen( GravityCompat.START ) ){
drawer_layout.closeDrawer( GravityCompat.START )
}
else{
super.onBackPressed()
}
}
override fun onCreateOptionsMenu( menu: Menu ): Boolean {
/*
* Infla o menu. Adiciona itens a barra de topo, se
* ela estiver presente.
* */
menuInflater.inflate( R.menu.main, menu )
return true
}
override fun onOptionsItemSelected( item: MenuItem ): Boolean {
/*
* Lidar com cliques de itens da barra de ação aqui.
* A barra de ação manipulará automaticamente os
* cliques no botão Home / Up, desde que seja
* especificada uma atividade pai em AndroidManifest.xml.
* */
when( item.itemId ){
R.id.action_settings -> return true
else -> return super.onOptionsItemSelected( item )
}
}
override fun onNavigationItemSelected( item: MenuItem ): Boolean {
/*
* Lida com cliques nos itens do menu gaveta.
* */
when( item.itemId ){
R.id.nav_camera -> { }
R.id.nav_gallery -> { }
R.id.nav_slideshow -> { }
R.id.nav_manage -> { }
R.id.nav_share -> { }
R.id.nav_send -> { }
}
drawer_layout.closeDrawer( GravityCompat.START )
return true
}
}
Muitos dos códigos anteriores, incluindo os XMLs, serão removidos ou atualizados.
Aqui somente foram apresentados os recursos que serão trabalhados, os muito outros recursos em /res serão removidos do projeto.
Imagem de abertura de aplicativo
Para o primeiro recurso apresentado assim que o aplicativo é acionado pelo usuário, a imagem de background de abertura, vamos utilizar o recurso a seguir:
A imagem acima foi retirada do protótipo estático do projeto, resumidamente:
- Um printscreen foi realizado;
- E depois foram removidas as bordas que não pertencem a imagem requerida.
Os tamanhos esperados para a imagem de abertura são:
- drawable-mdpi: 320x480 pixels;
- drawable-hdpi: 480x800 pixels;
- drawable-xhdpi: 640x960 pixels;
- drawable-xxhdpi: 960x1600 pixels.
Você notará que será necessário dois ajustes de dimensões de imagem:
- 960x1600 que permite a fácil diminuição de dimensões para 480x800;
- 640x960 que permite a fácil diminuição de dimensões para 320x480.
Qualquer software popular de edição imagens deixará você realizar esses ajustes com facilidade. Existem até mesmo ferramentas online e gratuitas que permitem isso.
Apesar de para este projeto ter sido utilizado os programas Adobe Photoshop e Adobe Fireworks é possível fazer tudo com o GIMP. No curso de prototipagem Android do Blog é apresentado como também utilizar o GIMP para edição de imagens.
Folders drawable
Você notará que no projeto há também os diretórios, folders, de recursos: /drawable; /drawable-v21; e /drawable-v24.
Porém não há os aguardados drawable dpi. Vamos adiciona-los como a seguir:
- Com o botão direito do mouse clique em /res;
- Logo depois acesse New;
- Então clique em Directory;
- Coloque como rótulo o nome drawable-mdpi e clique em Ok.
O procedimento anterior, repita ele para os folders: /drawable-hdpi; /drawable-xhdpi; e /drawable-xxhdpi.
E os folders mipmap, não podemos utiliza-los?
Na verdade podemos, mas esses folders são para o ícone de abertura do aplicativo, aquele que fica na área de aplicativos do sistema Android.
Voltando aos folders drawable... ao final da criação deles teremos em /res:
Você deve ter notado que não criamos o folder /drawable-ldpi e /drawable-xxxhdpi, certo? Isso, pois segundo informações em comunidade e na própria documentação oficial: não há mais suporte para ldpi em aparelhos smartphones e tablets; e o xxxhdpi é somente para o ícone de abertura de aplicativo, o que fica em folder mipmap.
Com isso podemos partir para o download das imagens de abertura, caso você já não as tenha feito.
Download das imagens
Para adiantar o processo, você pode realizar o download da imagem de abertura em suas diferentes versões, isso nos links a seguir:
- /res/drawable-mdpi/background.jpg;
- /res/drawable-hdpi/background.jpg;
- /res/drawable-xhdpi/background.jpg;
- /res/drawable-xxhdpi/background.jpg.
Por fim, coloque as imagens em seus respectivos folders de projeto.
Definindo a imagem como background de abertura
Para definir a imagem escolhida como background de abertura de aplicativo ainda é necessária uma atualização no arquivo de tema. Em /res/values/styles.xml coloque o código em destaque:
...
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!--
Definição da imagem que será carregada como imagem de
background em abertura de aplicativo.
-->
<item name="android:windowBackground">@drawable/background</item>
...
</style>
...
Imagem de fundo das atividades
A imagem de background das atividades de projeto é muito similar a imagem de background de abertura de aplicativo, somente não tem o rótulo central "BlueShoes". Veja-a seguir:
Para a criação dela o procedimento foi similar ao da imagem de background de abertura de app. Essa também foi retirada do protótipo estático do projeto:
- Primeiro o printscreen na imagem sem o rótulo central;
- Depois os cortes de bordas que não são parte da imagem.
Novamente, os tamanhos aguardados como imagem de background de atividades são:
- drawable-mdpi: 320x480 pixels;
- drawable-hdpi: 480x800 pixels;
- drawable-xhdpi: 640x960 pixels;
- drawable-xxhdpi: 960x1600 pixels.
Porém dessa vez também temos de fornecer as imagens de background em orientação landscape, horizontal. Para isso teremos como base a imagem a seguir:
E os tamanhos esperados para ela são:
- drawable-land-mdpi: 480x320 pixels;
- drawable-land-hdpi: 800x480 pixels;
- drawable-land-xhdpi: 960x640 pixels;
- drawable-land-xxhdpi: 1600x960 pixels.
Para criar as imagens você pode utilizar qualquer software de edição de imagens que permita corte e redimensionamento. Na sessão Imagem de abertura de aplicativo foram indicados alguns softwares.
Folders drawable para landscape
Já temos os diretórios para as imagens de background em versão portrait. Agora precisamos dos folders para imagens em landscape.
A seguir o passo a passo para a criação destes folders:
- Com o botão direito do mouse clique em /res;
- Logo depois acesse New;
- Então clique em Directory;
- Coloque como rótulo o nome drawable-land-mdpi e clique em Ok.
Repita o procedimento anterior para os folders: /drawable-land-hdpi; /drawable-land-xhdpi; e /drawable-land-xxhdpi.
Ao final da criação teremos:
Com isso podemos partir para o download das imagens de background de atividade.
Download das imagens
Para adiantar o desenvolvimento, realize o download da imagem de background de atividades em suas diferentes versões, nos links a seguir:
- Portrait:
- Landscape:
Coloque as imagens em seus respectivos folders de projeto.
Definindo a imagem como background de atividade
No layout /res/layout/app_bar_main.xml, carregado na única atividade do projeto, coloque a seguinte nova configuração de background no ViewGroup raiz:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
...
android:background="@drawable/bg_activity">
...
Ícone de abertura de aplicativo
A imagem do ícone de abertura do aplicativo é derivada da imagem de background de abertura de app. Veja-a a seguir:
A criação dela foi simples como no caso das outras imagens já discutidas até aqui:
- Primeiro o printscreen na imagem de background com rótulo central;
- Depois o corte dela para mante-la quadrada.
Para ícone de lançamento de aplicativo os tamanhos aguardados são:
- mipmap-mdpi: 48x48 pixels;
- mipmap-hdpi: 72x72 pixels;
- mipmap-xhdpi: 96x96 pixels;
- mipmap-xxhdpi: 144x144 pixels;
- mipmap-xxxhdpi: 192x192 pixels.
Desta vez, para a criação do recurso, vamos utilizar uma ferramenta online gratuita que permite a rápida geração de ícones para projetos Android.
Android Asset Studio
Primeiro, certifique-se de que o ícone de abertura de aplicativo tem ao menos as dimensões 192x192. Logo depois siga o roteiro abaixo:
- Acesse o site Android Asset Studio;
- Clique em Launcher icon generator;
- Em seguida clique em Image e carregue a imagem quadrada criada para ser o ícone de abertura do app;
- Coloque o Padding como 0%;
- Certifique-se de que em Name está ic_launcher (nome comum para ícones de abertura de app);
- Por fim clique no botão azul, no topo a direita, Download ZIP.
Com o ZIP descarregado, descompacte o arquivo e então coloque no projeto os ícones presentes nos folders /mipmap. Basta copiar os folders /mipmap e então cola-los dentro do folder /res:
- Clique em Ok para Copy;
- Então clique em Overwrite for all.
Ainda é preciso criar a versão circular do ícone de abertura de app, para isso repita todo o processo de carregamento de imagem no Android Asset Studio, porém com as seguintes alterações:
- Em Shape selecione Circle;
- Em Name coloque ic_launcher_round.
Descarregue o ZIP da versão circular do ícone de abertura. Descompacte ele e então copie os folders /mipmap e os coloque em projeto, como feito anteriormente.
Ao final do carregamento das versões do ícone de abertura teremos o seguinte nos principais folders /mipmap:
Download das imagens
A seguir os links para download do ícone de abertura de aplicativo em suas diferentes versões. Primeiro os links do ícone em versão retangular:
- /res/mipmap-mdpi/ic_launcher.jpg;
- /res/mipmap-hdpi/ic_launcher.jpg;
- /res/mipmap-xhdpi/ic_launcher.jpg;
- /res/mipmap-xxhdpi/ic_launcher.jpg;
- /res/mipmap-xxxhdpi/ic_launcher.jpg.
Agora os links do ícone em versão circular:
- /res/mipmap-mdpi/ic_launcher_round.jpg;
- /res/mipmap-hdpi/ic_launcher_round.jpg;
- /res/mipmap-xhdpi/ic_launcher_round.jpg;
- /res/mipmap-xxhdpi/ic_launcher_round.jpg;
- /res/mipmap-xxxhdpi/ic_launcher_round.jpg.
Coloque as imagens em seus respectivos diretórios de projeto.
Definição do menu gaveta
Vamos agora as atualizações, principalmente, em código dinâmico. Atualizações para a criação dos dois possíveis menus gaveta.
Entendendo o funcionamento
Primeiro, o que temos até o momento como layout da atividade principal, /res/layout/activity_main.xml, é o seguinte:
<?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_main"
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:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/>
</android.support.v4.widget.DrawerLayout>
No código acima é importante entender o funcionamento do DrawerLayout. Primeiro que este componente de layout trabalha de maneira similar ao FrameLayout, sendo assim, as primeiras Views ficam abaixo das Views posteriores no eixo Z.
Mas o importante é saber que o primeiro componente visual filho será tratado como o componente que armazenará o conteúdo principal em tela. No código anterior o layout incluído, app_bar_main.xml, é responsável pelo conteúdo principal da atividade.
O segundo componente visual filho é responsável por conter o conteúdo que entrará na tela no formato "menu gaveta". No código anterior a View responsável pelo conteúdo gaveta é a NavigationView.
Muitos desenvolvedores que estão iniciando no Android acreditam que ou o NavigationView, ou o ListView, ou o RecyclerView, que estes têm de ser utilizados como gaveta, quando na verdade qualquer View pode ser utilizada.
O que é esperado ser respeitado é o uso de apenas dois filhos diretos, que no conteúdo anterior são: o layout app_bar_main.xml (CoordinatorLayout é o ViewGroup raiz) e o NavigationView (que adiciona outros dois componentes - cabeçalho e menu).
Resumo: em ambos os lados do DrawerLayout podemos colocar o que quisermos. Veja o menu gaveta do aplicativo Discord:
Entendendo isso, podemos partir para o desenvolvimento do menu personalizado do projeto BlueShoes.
Cabeçalho de menu para usuário não conectado
Vamos primeiro desenvolver a parte mais simples, o cabeçalho de menu gaveta para usuário não conectado.
Esse cabeçalho tem uma imagem de background, imagem utilizada no protótipo estático do projeto:
A construção desta imagem foi similar ao das imagens de background apresentadas em tópicos anteriores:
- Primeiro foi realizado o printscreen;
- Depois o corte das bordas utilizando algum software de edição de imagem.
É esperado que o menu gaveta, segundo a documentação oficial, tenha no máximo uma largura de 320dp. Sendo assim nossa imagem de background tem de ter no máximo essa largura, digo essa largura de acordo com o folder /drawable, pois ela terá quatro versões: mdpi; hdpi; xhdpi; e xxhdpi.
Para saber quais seriam estes tamanhos, você poderia utilizar o site Pixplicity | DP/PX converter e então informar o valor em teste, em nosso caso é 320dp:
Sendo assim nossa imagem de background de cabeçalho terá de ter as larguras:
- drawable-mdpi: 320 pixels;
- drawable-hdpi: 480 pixels;
- drawable-xhdpi: 640 pixels;
- drawable-xxhdpi: 960 pixels.
Todo esse trabalho já foi feito para ti. Também temos como objetivo aqui mostrar ferramentas que você poderá utilizar em seu dia a dia como desenvolvedor. A seguir os links para download das diferentes versões da imagem de cabeçalho:
- /res/drawable-mdpi/nav_header.jpg;
- /res/drawable-hdpi/nav_header.jpg;
- /res/drawable-xhdpi/nav_header.jpg;
- /res/drawable-xxhdpi/nav_header.jpg.
Agora, em /res/layout, crie o arquivo nav_header_user_not_logged.xml como a seguir:
- Clique com o botão direito do mouse em /res/layout;
- Acesse New;
- Clique em Layout resource file;
- Coloque como nome do arquivo nav_header_user_not_logged.xml e clique em OK.
Como conteúdo deste novo layout coloque:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_header_user_not_logged"
android:background="@drawable/nav_header"
android:paddingRight="40dp"
android:paddingLeft="40dp"
android:paddingTop="30dp"
android:paddingBottom="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/bt_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:background="@drawable/bt_nav_header_login_bg"
android:textColor="@android:color/white"
android:textAllCaps="false"
android:text="@string/tx_login"/>
<TextView
android:id="@+id/tv_or"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/bt_login"
android:layout_toEndOf="@+id/bt_login"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:textColor="@color/colorText"
android:text="@string/tx_or"/>
<TextView
android:id="@+id/tv_sign_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tv_or"
android:layout_toEndOf="@+id/tv_or"
android:layout_centerVertical="true"
android:textColor="@color/colorLink"
android:text="@string/tx_me_sign_up"/>
</RelativeLayout>
Note que em <Button> temos um background personalizado, /res/drawable/bt_nav_header_login_bg.xml. Esse é o background que além de definir a cor e bordas arredondadas defini também o Ripple Effect para aparelhos com o Android API 21, Lollipop, ou superior.
A API de suporte do Android aplica o efeito ripple a partir da API 21, então primeiro temos de criar um arquivo bt_nav_header_login_bg.xml geral, que funcione sem definição específica de versão de API Android.
Logo, no folder /res/drawable crie um novo XML como a seguir:
- Clique com o botão direito do mouse em /res/drawable;
- Acesse New;
- Clique em Drawable resource file;
- Em File name coloque bt_nav_header_login_bg.xml;
- Em Root element coloque shape;
- Clique em OK.
Como conteúdo deste novo arquivo coloque:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--
Definição da cor de background do shape
retangular.
-->
<solid android:color="@color/colorNavButton" />
<!--
Definição da curvatura das pontas do shape
retangular.
-->
<corners android:radius="3dp"/>
</shape>
Agora a versão de background de botão com o efeito ripple ativado. Em /res crie o folder /drawable-v21. Este novo folder conterá recursos que serão utilizados pelo sistema Android somente quando em API 21 ou superior.
Agora, dentro do novo folder /res/drawable-v21, crie um arquivo XML como feito anteriormente, com o mesmo nome e configuração do último arquivo XML criado.
Dentro do novo arquivo /res/drawable-v21/bt_nav_header_login_bg.xml coloque o seguinte código XML:
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorNavButton" />
<corners android:radius="3dp"/>
</shape>
</item>
</ripple>
Note que as configurações de shape são as mesmas, porém estão "envelopadas" pela API de efeito ripple.
Abaixo o diagrama do layout nav_header_user_not_logged.xml:
Com isso nosso cabeçalho de usuário não conectado está pronto.
Cabeçalho de menu para usuário conectado
Para o cabeçalho de menu gaveta quando o usuário estiver conectado teremos somente informações de qual usuário está conectado, não haverá ação (botão ou link) nesta parte do menu.
Essas informações são importantes para que o usuário saiba qual conta está conectada ao aplicativo.
Antes de prosseguir com o código do layout, primeiro vamos adicionar uma API que permitirá, em poucas linhas de código, colocarmos um ImageView redondo no layout.
No Gradle Level de Projeto, build.gradle (Project: BlueShoes), adicione a referência em destaque a seguir:
...
allprojects {
repositories {
google()
jcenter()
mavenCentral() /* Para acesso a API RoundedImageView */
}
}
...
Agora no Gradle Level de Aplicativo, build.gradle (Module: app), adicione a referência em destaque, referência a API RoundedImageView:
...
dependencies {
...
/* RoundedImageView API */
implementation 'com.makeramen:roundedimageview:2.3.0'
}
Por fim sincronize o projeto.
Ainda temos de adicionar uma nova fonte ao projeto, fonte que será utilizada no rótulo "Conectado como":
Aqui o fonte será ainda Roboto, porém a versão light desta fonte. Foi utilizado o Google Fonts para baixar a fonte, mas se você quiser saber passo a passo como prosseguir com o trabalho com fontes personalizadas no Android, então acesse os artigos a seguir:
Agora no folder /res crie um novo folder, /font. E então coloque a fonte roboto_light.ttf dentro deste folder. Você pode baixar esta fonte diretamente do GitHub do projeto, em:
Assim, como feito no tópico anterior, em /res/layout crie um novo layout XML, desta vez com o nome nav_header_user_logged.xml e com a seguinte codificação XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rl_header_user_logged"
android:background="@drawable/nav_header"
android:paddingRight="40dp"
android:paddingLeft="40dp"
android:paddingTop="30dp"
android:paddingBottom="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/iv_user"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:scaleType="centerCrop"
app:riv_border_color="@android:color/transparent"
app:riv_border_width="0dp"
app:riv_corner_radius="54dp"
app:riv_mutate_background="false"
app:riv_oval="true"
app:riv_tile_mode="clamp"/>
<TextView
android:id="@+id/tv_connected_as"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_user"
android:layout_toRightOf="@+id/iv_user"
android:layout_toEndOf="@+id/iv_user"
android:layout_marginTop="8dp"
android:textSize="12sp"
android:textColor="@color/colorText"
android:fontFamily="@font/roboto_light"
android:text="@string/tx_conected_as"/>
<TextView
android:id="@+id/tv_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/iv_user"
android:layout_toEndOf="@+id/iv_user"
android:layout_below="@+id/tv_connected_as"
android:layout_marginTop="-4dp"
android:maxLines="1"
android:ellipsize="end"
android:textSize="20sp"
android:textColor="@color/colorText"
android:textStyle="bold"/>
</RelativeLayout>
Abaixo o diagrama do layout anterior:
Assim podemos partir para as duas listas de itens de menu.
Listas de itens
Teremos duas listas de itens:
- Uma para qualquer status do usuário:
- Contendo as categorias de calçados e alguns links que devem ser passíveis de acesso independente do status do usuário.
- Outra para somente quando o usuário estiver conectado:
- Será a lista ao fundo do menu gaveta;
- Conterá itens específicos para usuários conectados, como, por exemplo, lista de pedidos.
Iremos trabalhar com dois RecyclerViews. De acordo com o status do usuário (conectado ou não) a segunda lista será ou não apresentada, juntamente com o cabeçalho correto.
Antes de prosseguirmos para a codificação da primeira lista, vamos criar um arquivo de layout que conterá todos os componentes de menu gaveta.
Em /res/layout crie o layout nav_menu.xml com o código a seguir:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_menu_drawer"
android:orientation="vertical"
android:layout_gravity="start"
android:layout_width="320dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<include layout="@layout/nav_header_user_not_logged"/>
<include layout="@layout/nav_header_user_logged"/>
<!-- Código da primeira e principal lista de itens. -->
<!-- Código da segunda lista de itens. -->
</LinearLayout>
Note que os <include> dos cabeçalhos já estão ocorrendo. Em código dinâmico trabalharemos qual será apresentado e qual será escondido de acordo com o status do usuário.
Ainda temos de atualizar o layout /res/layout/activity_main.xml para já adicionar o nav_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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_main" />
<include layout="@layout/nav_menu" />
</android.support.v4.widget.DrawerLayout>
Note que o <include layout="@layout/nav_menu" /> entrou no lugar da View <NavigationView>, que não mais será utilizada neste projeto.
Também removemos os atributos android:layout_width e android:layout_height do primeiro <include>, pois este layout vinculado via <include>, app_bar_main.xml, será utilizado somente neste ponto do projeto, a principio. Então não é necessário o fornecimento de valores extras de atributos que não diferem dos já definidos no layout incluído.
Classe de domínio para itens de menu gaveta
Antes de prosseguirmos com a codificação das listas, vamos primeiro definir uma classe de domínio para conter as informações de itens.
Na raiz do projeto crie um novo pacote, /domain:
- Clique com o botão direito do mouse na raiz do projeto;
- Então acesse New;
- Logo após clique em Package;
- Como nome coloque domain e clique em OK.
Agora crie uma nova classe com o rótulo NavMenuItem:
- Em /domain clique com o botão direito do mouse;
- Acesse New;
- Clique em Kotlin File / Class;
- Em Name coloque NavMenuItem;
- Em Kind selecione Class;
- Clique em OK.
Coloque como código inicial de NavMenuItem o seguinte:
class NavMenuItem(
val label : String,
val iconId : Int = DEFAULT_ICON_ID ){
companion object {
const val DEFAULT_ICON_ID = -1
}
}
Como alguns itens não têm ícone, a constante DEFAULT_ICON_ID será utilizada em ao menos uma lógica de negócio do projeto, mais precisamente no adaptador de itens de lista, para que os componentes visuais corretos do layout de item se mantenham em tela.
Base de dados dos itens de menu
Ainda temos de ter uma classe de "persistência" dos itens de menu, isso para manter todo o código bem dividido.
Na raiz do projeto crie um novo pacote, /data. Logo depois, neste novo pacote, crie a classe NavMenuItemsDataBase com o código a seguir:
class NavMenuItemsDataBase( context: Context ) {
/*
* Itens de menu gaveta que sempre estarão presentes,
* independente do status do usuário (conectado ou
* não).
* */
val items = listOf(
NavMenuItem(
context.getString(R.string.item_all_shoes)
),
NavMenuItem(
context.getString(R.string.item_flip_flops)
),
NavMenuItem(
context.getString(R.string.item_cleats)
),
NavMenuItem(
context.getString(R.string.item_sandals)
),
NavMenuItem(
context.getString(R.string.item_ballet_shoes)
),
NavMenuItem(
context.getString(R.string.item_suit_shoes)
),
NavMenuItem(
context.getString(R.string.item_shoes)
),
NavMenuItem(
context.getString(R.string.item_performance_shoes)
),
NavMenuItem(
context.getString(R.string.item_contact),
R.drawable.ic_email_black_24dp
),
NavMenuItem(
context.getString(R.string.item_about),
R.drawable.ic_domain_black_24dp
),
NavMenuItem(
context.getString(R.string.item_privacy_policy),
R.drawable.ic_shield_lock_black_24dp
)
)
/*
* Itens de menu gaveta que estarão presentes somente
* quando o usuário estiver conectado.
* */
val itemsLogged = listOf(
NavMenuItem(
context.getString(R.string.item_my_orders),
R.drawable.ic_package_variant_closed_black_24dp
),
NavMenuItem(
context.getString(R.string.item_settings),
R.drawable.ic_settings_black_24dp
),
NavMenuItem(
context.getString(R.string.item_sign_out),
R.drawable.ic_exit_run_black_24dp
)
)
}
Essa classe não é uma classe de dados simulados, mock. Os dados nela são os que serão utilizados em produção.
Ícones dos itens
No tópico anterior você deve ter notado que alguns itens têm ícones os acompanhando, como também apresentado em protótipo estático.
Todos foram descarregados do site Material Design Icons. Mesmo este não sendo o repositório oficial de ícones do Android, ele é o mais completo e útil.
Dica: em Material Design Icons, quando você escolher um ícone, clique nele e logo depois clique em Icon Package e então selecione Android 5.x. Assim você terá várias versões do mesmo ícone e poderá utilizar a necessária em projeto sem auxílio de software de edição de imagem. Aqui ficaremos com as versões de 24dp, tamanho padrão para ícones de itens de menu gaveta.
Você pode estar baixando todos os ícones de itens direto dos links a seguir:
- Ícone do item Contato:
- Ícone do item Sobre:
- Ícone do item Políticas de privacidade:
- Ícone do item Meus pedidos:
- Ícone do item Minhas configurações:
- Ícone do item Sair:
Adapter dos itens
Para a classe adaptadora de itens, a mesma que será utilizada em ambas as listas possíveis, vamos iniciar com o layout de item.
Em /res/layout crie o novo layout XML nav_menu_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingRight="16dp"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="48dp">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="32dp"
android:layout_marginEnd="32dp"
android:layout_gravity="center_vertical"
android:tint="@color/colorText"/>
<TextView
android:id="@+id/tv_label"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorText"/>
</LinearLayout>
A seguir o diagrama do layout anterior:
Note que os espaçamentos, alturas e larguras do layout de item estão respeitando as exigências em documentação oficial de menu gaveta no Material Design.
Agora, na raiz do projeto, onde se encontra a MainActivity, crie uma nova classe Kotlin, classe com o rótulo NavMenuItemAdapter. Coloque nesta nova entidade o seguinte código de classe adaptadora de itens do RecyclerView:
class NavMenuItemsAdapter( val items: List<NavMenuItem> ) :
RecyclerView.Adapter<NavMenuItemsAdapter.ViewHolder>() {
lateinit var selectionTracker: SelectionTracker<Long>
override fun onCreateViewHolder(
parent: ViewGroup,
type: Int ): ViewHolder {
val layout = LayoutInflater
.from( parent.context )
.inflate(
R.layout.nav_menu_item,
parent,
false
)
return ViewHolder( layout )
}
override fun onBindViewHolder(
holder: ViewHolder,
position: Int ) {
holder.setData( items[ position ] )
}
override fun getItemCount() = items.size
inner class ViewHolder( itemView: View ) :
RecyclerView.ViewHolder( itemView ){
private val ivIcon : ImageView
private val tvLabel : TextView
init{
ivIcon = itemView.findViewById( R.id.iv_icon )
tvLabel = itemView.findViewById( R.id.tv_label )
}
fun setData( item: NavMenuItem ){
tvLabel.text = item.label
if( item.iconId != NavMenuItem.DEFAULT_ICON_ID ){
ivIcon.setImageResource( item.iconId )
ivIcon.visibility = View.VISIBLE
}
else{
ivIcon.visibility = View.GONE
}
}
}
}
Note o bloco condicional em setData() para corretamente apresentar o layout de cada item em lista, com ícone ou sem ícone.
Lista principal, contendo categorias de calçados
No layout /res/layout/nav_menu.xml adicione o seguinte RecyclerView em destaque:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
...
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_menu_items"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:scrollbars="vertical"/>
...
</LinearLayout>
O atributo android:layout_weight="1" está sendo utilizado, pois o único componente, segundo protótipo estático, que pode ter barra de rolagem é o da lista de categorias e itens principais. O cabeçalho e a lista de fundo, quando ativa, não podem ter rolagem. Por isso também o uso do LinearLayout como ViewGroup principal.
Agora vamos a inicialização da lista de itens, inicialização dentro da atividade principal. Na MainActivity adicione o método a seguir:
...
/*
* Método que inicializa a lista de itens de menu gaveta
* que estará presente quando o usuário estiver ou não
* conectado ao aplicativo.
* */
private fun initNavMenuItems(){
rv_menu_items.setHasFixedSize( false )
rv_menu_items.layoutManager = LinearLayoutManager( this )
rv_menu_items.adapter = NavMenuItemsAdapter( NavMenuItemsDataBase( this ).items )
}
...
Em tópicos posteriores criaremos ainda mais métodos responsáveis pela inicialização de todo o menu gaveta.
Lista de itens para usuário conectado
Ainda no layout /res/layout/nav_menu.xml adicione os seguintes componentes visuais em destaque:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
...
<View
android:id="@+id/v_nav_vertical_line"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/colorViewLine"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_menu_items_logged"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"/>
</LinearLayout>
A View foi adicionada para representar a linha superior da segunda lista de itens, como definido em protótipo estático:
Com isso podemos ir a inicialização da lista de itens para usuário conectado, inicialização também dentro da atividade principal. Na MainActivity adicione o método a seguir:
...
/*
* Método que inicializa a parte de lista de itens de menu
* gaveta que estará presente somente quando o usuário
* estiver conectado ao aplicativo.
* */
private fun initNavMenuItemsLogged(){
rv_menu_items_logged.setHasFixedSize( true )
rv_menu_items_logged.layoutManager = LinearLayoutManager( this )
rv_menu_items_logged.adapter = NavMenuItemsAdapter( NavMenuItemsDataBase( this ).itemsLogged )
}
...
E a seleção dos itens? Como será possível?
Se você conhece o RecyclerView deve estar se perguntando como se dará a seleção dos itens, qual lógica de negócio será utilizada, até porque o item de menu gaveta selecionado deverá estar em destaque e acionar um novo conteúdo na tela.
Temos dois RecyclerViews, logo sabemos que qualquer algoritmo de seleção de item adicionado terá de ter a parte de sincronia entre as duas listas, isso, pois a seleção em uma lista deve remover a seleção do item anterior, contido nela ou na outra lista.
Como resposta ao problema da seleção de itens, vamos utilizar a SelectionTracker, API nativa do Android e criada somente para isso: permitir a seleção de itens no RecyclerView.
Para saber mais sobre a API de seleção no RecyclerView entre no artigo a seguir: SelectionTracker Para Seleção de Itens no RecyclerView Android.
Instalação da API de seleção e atualizações de apoio
Nosso primeiro passo com a SelectionTracker é adiciona-la ao projeto. No Gradle Level de Aplicativo, build.gradle (Module: app), adicione a referência em destaque e logo depois sincronize o projeto:
...
dependencies {
...
/* SelectionTracker API */
implementation 'com.android.support:recyclerview-selection:28.0.0'
}
Se você estudou a SelectionTracker deve saber que teremos de ter um identificador único para os objetos que serão utilizados em seleção.
Em nosso domínio de problema esses objetos serão todos do tipo NavMenuItem, porém nesta classe não temos nenhuma propriedade para guardar um identificador único. Até poderíamos utilizar o rótulo de cada item, mas isso certamente será um problema em atualizações futuras do projeto, pois a função do rótulo não é ser único.
Sendo assim, vamos atualizar a classe NavMenuItem para suportar também uma propriedade id. Segue atualização:
class NavMenuItem(
val id: Long,
val label : String,
val iconId : Int = DEFAULT_ICON_ID ){
companion object {
const val DEFAULT_ICON_ID = -1
}
}
Antes de atualizarmos a classe de dados que contém os itens de menu gaveta, temos de ter em mente que os IDs que criaremos para os itens certamente serão únicos independente das atualizações que o projeto BlueShoes venha a sofrer. Devido a isso é possível que em lógicas de negócio futuras seja necessário o uso de alguns desses IDs.
Para que o uso de qualquer um dos IDs de itens em lógicas do projeto não venha a ser um problema de "valor mágico", por exemplo, algo que seguramente atrasaria qualquer atualização no menu gaveta. Para evitar isso, vamos definir os IDs em um arquivo XML específico para identificadores únicos.
Em /res/values crie um novo arquivo XML chamado nav_menu_items_ids.xml e com o seguinte código estático:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Segundo testes, foi observado que os valores inteiros
dos IDs são adicionados na ordem crescente de
declaração de <itens>.
-->
<item name="item_all_shoes" type="id" format="integer" />
<item name="item_flip_flops" type="id" format="integer" />
<item name="item_cleats" type="id" format="integer" />
<item name="item_sandals" type="id" format="integer" />
<item name="item_ballet_shoes" type="id" format="integer" />
<item name="item_suit_shoes" type="id" format="integer" />
<item name="item_shoes" type="id" format="integer" />
<item name="item_performance_shoes" type="id" format="integer" />
<item name="item_contact" type="id" format="integer" />
<item name="item_about" type="id" format="integer" />
<item name="item_privacy_policy" type="id" format="integer" />
<item name="item_my_orders" type="id" format="integer" />
<item name="item_settings" type="id" format="integer" />
<item name="item_sign_out" type="id" format="integer" />
</resources>
Com isso podemos atualizar a classe NavMenuItemDataBase. Adicione os códigos em destaque:
class NavMenuItemsDataBase( context: Context ) {
/*
* Itens de menu gaveta que sempre estarão presentes,
* independente do status do usuário (conectado ou
* não).
* */
val items = listOf(
NavMenuItem(
R.id.item_all_shoes.toLong(),
context.getString(R.string.item_all_shoes)
),
NavMenuItem(
R.id.item_flip_flops.toLong(),
context.getString(R.string.item_flip_flops)
),
NavMenuItem(
R.id.item_cleats.toLong(),
context.getString(R.string.item_cleats)
),
NavMenuItem(
R.id.item_sandals.toLong(),
context.getString(R.string.item_sandals)
),
NavMenuItem(
R.id.item_ballet_shoes.toLong(),
context.getString(R.string.item_ballet_shoes)
),
NavMenuItem(
R.id.item_suit_shoes.toLong(),
context.getString(R.string.item_suit_shoes)
),
NavMenuItem(
R.id.item_shoes.toLong(),
context.getString(R.string.item_shoes)
),
NavMenuItem(
R.id.item_performance_shoes.toLong(),
context.getString(R.string.item_performance_shoes)
),
NavMenuItem(
R.id.item_contact.toLong(),
context.getString(R.string.item_contact),
R.drawable.ic_email_black_24dp
),
NavMenuItem(
R.id.item_about.toLong(),
context.getString(R.string.item_about),
R.drawable.ic_domain_black_24dp
),
NavMenuItem(
R.id.item_privacy_policy.toLong(),
context.getString(R.string.item_privacy_policy),
R.drawable.ic_shield_lock_black_24dp
)
)
/*
* Itens de menu gaveta que estarão presentes somente
* quando o usuário estiver conectado.
* */
val itemsLogged = listOf(
NavMenuItem(
R.id.item_my_orders.toLong(),
context.getString(R.string.item_my_orders),
R.drawable.ic_package_variant_closed_black_24dp
),
NavMenuItem(
R.id.item_settings.toLong(),
context.getString(R.string.item_settings),
R.drawable.ic_settings_black_24dp
),
NavMenuItem(
R.id.item_sign_out.toLong(),
context.getString(R.string.item_sign_out),
R.drawable.ic_exit_run_black_24dp
)
)
fun getLastItemId() = items.last().id
fun getFirstItemLoggedId() = itemsLogged.first().id
}
Os métodos getLastItemId() e getFirstItemLoggedId() serão úteis em parte da lógica de seleção de itens, melhor explicada em classes posteriores, ligadas a API SelectionTracker.
Classes de permissão a seleção de item
A SelectionTracker API é acompanhada de um código boilerplate que não deixa nenhuma desenvolvedor satisfeito. Mas é algo obrigatório, então vamos a adição de algumas classes responsáveis pelo correto funcionamento da característica de seleção de itens.
Primeiro, na raiz do projeto crie um novo pacote com o rótulo util. Neste novo pacote crie uma nova classe Kotlin com o nome NavMenuItemKeyProvider e o código como a seguir:
/*
* Subclasse de ItemKeyProvider que fornece acesso a chaves de seleção
* estáveis, podendo ser de três tipos: Parcelable (e suas
* subclasses); String e Long.
* */
class NavMenuItemKeyProvider( val items: List<NavMenuItem> ) :
ItemKeyProvider<Long>( ItemKeyProvider.SCOPE_MAPPED ) {
/*
* Retorna a chave de seleção na posição fornecida do adaptador ou
* então retorna null.
* */
override fun getKey( position: Int ) = items[ position ].id
/*
* Retorna a posição correspondente à chave de seleção, ou
* RecyclerView.NO_POSITION em caso de null em getKey().
* */
override fun getPosition( key: Long )
= items.indexOf(
items.filter{
item -> item.id == key
}.single()
)
}
Importante: não deixe de ler os comentários de todos os códigos do projeto, eles complementam as explicações em artigo.
Ainda no pacote /util crie a classe Kotlin NavMenuItemDetails com o código abaixo:
/*
* Uma implementação de ItemDetails fornece à biblioteca de seleção
* acesso a informações sobre um específico item do RecyclerView. Esta
* classe é um componente chave no controle dos comportamentos da
* biblioteca de seleção no contexto de uma atividade específica.
* */
class NavMenuItemDetails(
var item: NavMenuItem? = null,
var adapterPosition: Int = -1
) : ItemDetailsLookup.ItemDetails<Long>() {
/*
* Retorna a posição do adaptador do item
* (ViewHolder.adapterPosition).
* */
override fun getPosition() = adapterPosition
/*
* Retorna a entidade que é a chave de seleção do item.
* */
override fun getSelectionKey() = item!!.id
/*
* Retorne "true" se o item tiver uma chave de seleção. Se true
* não for retornado o item em foco (acionado pelo usuário) não
* será selecionado.
* */
override fun inSelectionHotspot( e: MotionEvent ) = true
}
Agora, no mesmo pacote, crie a classe NavMenuItemDetailsLookup com o código a seguir:
/*
* ItemDetailsLookup permite que a biblioteca de seleção acesse
* informações sobre os itens do RecyclerView que receberam um
* MotionEvent. Ele é efetivamente uma factory para instâncias
* de ItemDetails que são submetidas a backup (ou extraídas)
* de uma ocorrência de RecyclerView.ViewHolder.
* */
class NavMenuItemDetailsLookup( val rvMenuItems: RecyclerView ) :
ItemDetailsLookup<Long>() {
/*
* Retorna o ItemDetails para o item sob o evento
* (MotionEvent) ou nulo caso não haja um.
* */
override fun getItemDetails( event: MotionEvent ): ItemDetails<Long>? {
val view = rvMenuItems.findChildViewUnder( event.x, event.y )
if( view != null ){
val holder = rvMenuItems.getChildViewHolder( view )
return (holder as NavMenuItemsAdapter.ViewHolder).itemDetails
}
return null
}
}
Note a referência ao adapter de listas de itens de menu: (holder as NavMenuItemsAdapter.ViewHolder).itemDetails. No próximo tópico adicionaremos as atualizações necessárias a essa classe adaptadora.
Por fim, ainda no pacote /util, adicione a classe responsável por conter a lógica de negócio de itens que podem ganhar ou perder seleção, incluindo o uso dos métodos getLastItemId() e getFirstItemLoggedId().
Segue código da classe NavMenuItemPredicate:
/*
* SelectionTracker.SelectionPredicate é utilizada para definir
* quais itens poderão ser selecionados e quantos deles.
*
* A parametrização deve ser do tipo da chave estável definida
* em ItemKeyProvider.
* */
class NavMenuItemPredicate( val activity: MainActivity ) :
SelectionTracker.SelectionPredicate<Long>() {
/*
* Retorne true se puder ter múltipla seleção e false para
* somente uma seleção.
* */
override fun canSelectMultiple() = false
/*
* Retorne true se o item referente a key puder ser definido
* como nextState.
* */
override fun canSetStateForKey(
key: Long,
nextState: Boolean ) : Boolean{
/*
* A lógica de negócio abaixo foi adotada para que não
* seja possível deixar o menu gaveta com nenhum item
* selecionado. Assim, se o status do item acionado for
* false (nextState = false) e se ele for o item já
* selecionado, então retornamos false para que o status
* dele não mude, continue como "selecionado".
* */
if( !nextState ){
val lastItemId = NavMenuItemsDataBase( activity ).getLastItemId()
val firstItemLoggedId = NavMenuItemsDataBase( activity ).getFirstItemLoggedId()
val sizeNavMenuItems = activity.selectNavMenuItems.selection.size()
val sizeNavMenuItemsLogged = activity.selectNavMenuItemsLogged.selection.size()
/*
* Há somente duas situações onde um item pode
* acionar canSetStateForKey() com nextState sendo
* false:
*
* 1ª - Quando o item está selecionado e então
* ele é acionado novamente, para que perca a
* seleção;
*
* 2ª - Quando é removida a seleção do item
* via deselect(), como estamos fazendo na
* atividade principal de projeto.
*
* No caso da 2ª situação, isso acontece porque
* temos dois objetos de seleção sendo utilizados,
* sendo assim, é preciso saber o intervalo do ID
* do item alvo, pois ele somente não perde a seleção
* se ele mesmo receber um novo acionamento. Em caso
* de item de outra lista, ele deve sim perder a
* seleção.
* */
if( (key <= lastItemId && sizeNavMenuItemsLogged == 0)
|| (key >= firstItemLoggedId && sizeNavMenuItems == 0) ){
return false
}
}
return true
}
/*
* Retorne true se o item referente a position puder ser definido
* como nextState. NÃO FUNCIONA COMO ESPERADO.
* */
override fun canSetStateAtPosition(
position: Int,
nextState: Boolean ) = true
}
Vamos às outras atualizações para que a SelectionTracker funcione como esperado.
Atualização no adapter de itens
Na classe adaptadora de itens de menu, NavMenuItemsAdapter, adicione os códigos em destaque:
class NavMenuItemsAdapter( ... ) : ... {
lateinit var selectionTracker: SelectionTracker<Long>
...
inner class ViewHolder( ... ) : ... {
...
val itemDetails: NavMenuItemDetails
init{
...
itemDetails = NavMenuItemDetails()
}
fun setData( item: NavMenuItem ){
...
/*
* São nos blocos condicionais a seguir que devem vir os
* algoritmos de atualização de UI, isso para indicar o
* item selecionado e os itens não selecionados.
* */
itemDetails.item = item
itemDetails.adapterPosition = adapterPosition
if( selectionTracker.isSelected( itemDetails.getSelectionKey() ) ){
itemView.setBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.colorNavItemSelected
)
)
}
else{
itemView.setBackgroundColor( Color.TRANSPARENT )
}
}
}
}
Classe de domínio, User
Vamos adicionar uma nova classe de domínio que permitirá, neste primeiro momento, o teste de mudança de menu gaveta.
Em /domain adicione a classe User com o código a seguir:
class User(
val name: String,
val image: Int,
val status: Boolean = false
)
Na atividade principal adicione o código abaixo em destaque:
class MainActivity : ... {
val user = User(
"Thiengo Vinícius",
R.drawable.user,
false
)
...
}
A imagem de teste, que está sendo utilizada como avatar do usuário fictício, pode ser baixada nos links a seguir:
- /res/drawable-mdpi/user.png;
- /res/drawable-hdpi/user.png;
- /res/drawable-xhdpi/user.png;
- /res/drawable-xxhdpi/user.png.
Inicialização dos SelectionTracker
Na atividade principal, para o SelectionTracker de itens de lista para usuários conectados ou não, adicione os códigos em destaque:
class MainActivity : ... {
...
lateinit var navMenuItems : List<NavMenuItem>
lateinit var selectNavMenuItems: SelectionTracker<Long>
...
private fun initNavMenuItems(){
rv_menu_items.setHasFixedSize( false )
rv_menu_items.layoutManager = LinearLayoutManager( this )
rv_menu_items.adapter = NavMenuItemsAdapter( navMenuItems )
initNavMenuItemsSelection()
}
/*
* Método responsável por inicializar o objeto de seleção
* de itens de menu gaveta, seleção dos itens que aparecem
* para usuário conectado ou não.
* */
private fun initNavMenuItemsSelection(){
selectNavMenuItems = SelectionTracker.Builder<Long>(
"id-selected-item",
rv_menu_items,
NavMenuItemKeyProvider( navMenuItems ),
NavMenuItemDetailsLookup( rv_menu_items ),
StorageStrategy.createLongStorage()
)
.withSelectionPredicate( NavMenuItemPredicate( this ) )
.build()
(rv_menu_items.adapter as NavMenuItemsAdapter).selectionTracker = selectNavMenuItems
}
...
}
Note que em initNavMenuItems(), além de termos adicionado a invocação ao método de configuração de selectNavMenuItems, initNavMenuItemsSelection(), também atualizamos o modo de inicialização do adapter de lista: rv_menu_items.adapter = NavMenuItemsAdapter( navMenuItems ).
A propriedade navMenuItems será iniciada em um método de inicialização do menu gaveta, que será discutido em tópico posterior.
Agora vamos ao código do SelectionTracker para itens de menu específicos para usuário conectado. Ainda na MainActivity, adicione os códigos em destaque:
class MainActivity : ... {
...
lateinit var navMenuItemsLogged : List<NavMenuItem>
lateinit var selectNavMenuItemsLogged: SelectionTracker<Long>
...
/*
* Método que inicializa a parte de lista de itens de menu
* gaveta que estará presente somente quando o usuário
* estiver conectado ao aplicativo.
* */
private fun initNavMenuItemsLogged(){
rv_menu_items_logged.setHasFixedSize( true )
rv_menu_items_logged.layoutManager = LinearLayoutManager( this )
rv_menu_items_logged.adapter = NavMenuItemsAdapter( navMenuItemsLogged )
initNavMenuItemsLoggedSelection()
}
/*
* Método responsável por inicializar o objeto de seleção
* de itens de menu gaveta, seleção dos itens que aparecem
* somente quando o usuário está conectado ao app.
* */
private fun initNavMenuItemsLoggedSelection(){
selectNavMenuItemsLogged = SelectionTracker.Builder<Long>(
"id-selected-item-logged",
rv_menu_items_logged,
NavMenuItemKeyProvider( navMenuItemsLogged ),
NavMenuItemDetailsLookup( rv_menu_items_logged ),
StorageStrategy.createLongStorage()
)
.withSelectionPredicate( NavMenuItemPredicate( this ) )
.build()
(rv_menu_items_logged.adapter as NavMenuItemsAdapter).selectionTracker = selectNavMenuItemsLogged
}
...
}
Para que ambos os SelectionTracker possam trabalhar o item selecionado em circunstâncias de reconstrução de atividade é necessário, como parte do código, o uso do onSaveInstanceState() como a seguir na MainActivity (adicione a está atividade):
...
override fun onSaveInstanceState( outState: Bundle? ) {
super.onSaveInstanceState( outState )
/*
* Para manter o item de menu gaveta selecionado caso
* haja reconstrução de atividade.
* */
selectNavMenuItems.onSaveInstanceState( outState!! )
selectNavMenuItemsLogged.onSaveInstanceState( outState )
}
...
E como será o acionamento de fragmentos?
Com o uso de RecyclerView não poderemos aproveitar o listener de item selecionado adicionado ao projeto na criação dele, o listener NavigationView.OnNavigationItemSelectedListener.
Tendo em mente que estamos trabalhando com objetos de seleção específicos do RecyclerView, temos então de utilizar alguma API de clique / seleção nestes objetos, porém com algum algoritmo de sincronia para que somente um item fique selecionado em menu gaveta, independente da lista de itens em foco.
Sendo assim podemos utilizar o método onItemStateChanged() da classe SelectionTracker.SelectionObserver. Na MainActivity adicione a seguinte classe interna:
...
inner class SelectObserverNavMenuItems(
val callbackRemoveSelection: ()->Unit
) : SelectionTracker.SelectionObserver<Long>(){
/*
* Método responsável por permitir que seja possível
* disparar alguma ação de acordo com a mudança de
* status de algum item em algum dos objetos de seleção
* de itens de menu gaveta. Aqui vamos proceder com
* alguma ação somente em caso de item obtendo seleção,
* para item perdendo seleção não haverá processamento,
* pois este status não importa na lógica de negócio
* deste método.
* */
override fun onItemStateChanged(
key: Long,
selected: Boolean ) {
super.onItemStateChanged( key, selected )
/*
* Padrão Cláusula de Guarda para não seguirmos
* com o processamento em caso de item perdendo
* seleção. O processamento posterior ao condicional
* abaixo é somente para itens obtendo a seleção,
* selected = true.
* */
if( !selected ){
return
}
/*
* Para garantir que somente um item de lista se
* manterá selecionado, é preciso acessar o objeto
* de seleção da lista de itens de usuário conectado
* para então remover qualquer possível seleção
* ainda presente nela. Sempre haverá somente um
* item selecionado, mas infelizmente o método
* clearSelection() não estava respondendo como
* esperado, por isso a estratégia a seguir.
* */
callbackRemoveSelection()
/*
* TODO: Mudança de Fragment
* */
/*
* Fechando o menu gaveta.
* */
drawer_layout.closeDrawer( GravityCompat.START )
}
}
...
Estamos trabalhando com um callback, callbackRemoveSelection, para que ambos os SelectionTracker possam utilizar a mesma classe observadora.
Sobre o padrão Cláusula de Guarda que está sendo utilizado, você consegue mais informações sobre ele no artigo a seguir: Padrão de Projeto: Cláusula de Guarda. É um padrão excelente e de fácil uso.
Ainda é preciso atualizar os métodos de inicialização de SelectionTracker. Adicione os códigos em destaque, primeiro para a propriedade selectNavMenuItems:
...
private fun initNavMenuItemsSelection(){
...
selectNavMenuItems.addObserver(
SelectObserverNavMenuItems {
selectNavMenuItemsLogged.selection.filter {
selectNavMenuItemsLogged.deselect( it )
}
}
)
(rv_menu_items.adapter as NavMenuItemsAdapter).selectionTracker = selectNavMenuItems
}
...
Agora para a propriedade selectNavMenuItemsLogged:
...
private fun initNavMenuItemsLoggedSelection(){
...
selectNavMenuItemsLogged.addObserver(
SelectObserverNavMenuItems {
selectNavMenuItems.selection.filter {
selectNavMenuItems.deselect( it )
}
}
)
(rv_menu_items_logged.adapter as NavMenuItemsAdapter).selectionTracker = selectNavMenuItemsLogged
}
...
Inicialização do menu gaveta
Agora podemos ir aos métodos e lógicas de negócio para a correta inicialização do menu gaveta.
Primeiro o método responsável pelo preenchimento de dados no cabeçalho de menu quando com usuário conectado. Na MainActivity adicione o método a seguir:
...
private fun fillUserHeaderNavMenu(){
if( user.status ) { /* Conectado */
iv_user.setImageResource(user.image)
tv_user.text = user.name
}
}
...
Agora o método responsável por esconder as Views corretas de acordo com o status do usuário (conectado ou não). Ainda na atividade principal adicione o método a seguir:
...
/*
* Método responsável por esconder itens do menu gaveta de
* acordo com o status do usuário (conectado ou não).
* */
private fun showHideNavMenuViews(){
if( user.status ){ /* Conectado */
rl_header_user_not_logged.visibility = View.GONE
fillUserHeaderNavMenu()
}
else{ /* Não conectado */
rl_header_user_logged.visibility = View.GONE
v_nav_vertical_line.visibility = View.GONE
rv_menu_items_logged.visibility = View.GONE
}
}
...
Assim o método principal com a lógica de inicialização do menu gaveta, initNavMenu():
...
/*
* Método de inicialização do menu gaveta, responsável por
* apresentar o cabeçalho e itens de menu de acordo com o
* status do usuário (logado ou não).
* */
private fun initNavMenu( savedInstanceState: Bundle? ){
val navMenu = NavMenuItemsDataBase( this )
navMenuItems = navMenu.items
navMenuItemsLogged = navMenu.itemsLogged
showHideNavMenuViews()
initNavMenuItems()
initNavMenuItemsLogged()
if( savedInstanceState != null ){
selectNavMenuItems.onRestoreInstanceState( savedInstanceState )
selectNavMenuItemsLogged.onRestoreInstanceState( savedInstanceState )
}
else{
/*
* O primeiro item do menu gaveta deve estar selecionado
* caso não seja uma reinicialização de tela / atividade.
* O primeiro item aqui é o de ID 1.
* */
selectNavMenuItems.select( R.id.item_all_shoes.toLong() )
}
}
...
O método anterior é invocado no onCreate() da atividade:
...
override fun onCreate( savedInstanceState: Bundle? ) {
...
initNavMenu( savedInstanceState )
}
...
Ajustes extras
Vamos criar um novo pacote na raiz do projeto, desta vez com o rótulo view. Logo em seguida arraste as classes MainActivity e NavMenuItemsAdapter para dentro deste pacote. Assim teremos a seguinte nova configuração física de projeto:
Removendo o que não é necessário
Neste ponto do projeto, podemos remover alguns arquivos e códigos que, a princípio, não serão mais úteis ao aplicativo.
Em /res remova:
- Arquivos:
- /drawabe/ic_launcher_background.xml;
- /drawabe/side_nav_bar.xml;
- /drawable-v21/ic_menu_camera.xml;
- /drawable-v21/ic_menu_gallery.xml;
- /drawable-v21/ic_menu_manage.xml;
- /drawable-v21/ic_menu_send.xml;
- /drawable-v21/ic_menu_share.xml;
- /drawable-v21/ic_menu_slideshow.xml;
- /layout/nav_header_main.xml;
- /menu/activity_main_drawer.xml;
- /values/drawable.xml.
- Diretórios (incluindo os arquivos dentro deles):
- /drawable-v24;
- /mipmap-anydpi-v26.
Agora na MainActivity remova os códigos a seguir em destaque:
class MainActivity :
AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener {
override fun onCreate( ... ) {
...
fab.setOnClickListener { view ->
Snackbar
.make(
view,
"Mude para a sua própria ação.",
Snackbar.LENGTH_LONG
)
.setAction(
"Ação",
null
)
.show()
}
...
nav_view.setNavigationItemSelectedListener( this )
}
...
override fun onNavigationItemSelected( item: MenuItem ): Boolean {
/*
* Lida com cliques nos itens do menu gaveta.
* */
when( item.itemId ){
R.id.nav_camera -> { }
R.id.nav_gallery -> { }
R.id.nav_slideshow -> { }
R.id.nav_manage -> { }
R.id.nav_share -> { }
R.id.nav_send -> { }
}
drawer_layout.closeDrawer( GravityCompat.START )
return true
}
}
Ainda na MainActivity, mas agora no layout /res/layout/app_bar_main.xml, remova o FloatingActionButton:
...
<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="@android:drawable/ic_dialog_email"/>
...
Em /res/values/dimens.xml remova as marcações em destaque:
<resources>
...
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>
Por fim, em /res/values/strings.xml remova os trechos em destaque:
<resources>
...
<string name="nav_header_title">Android Studio</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
...
</resources>
Assim podemos testar essa primeira parte desenvolvida do projeto.
Testes e resultados
Abra o Android Studio, vá em "Build", então em "Rebuid project". Ao final do rebuild execute o aplicativo em seu aparelho ou emulador Android de testes.
Assumindo, em primeiro teste, o usuário com o status "não conectado":
...
val user = User(
"Thiengo Vinícius",
R.drawable.user,
false
)
...
Temos:
Agora mudando o status do usuário para "conectado":
...
val user = User(
"Thiengo Vinícius",
R.drawable.user,
true
)
...
E executando novamente o projeto, temos:
Com isso finalizamos essa primeira parte de desenvolvimento do projeto Android de mobile-commerce. Não deixe de se inscrever na 📩 lista de emails do Blog para continuar acompanhando a série.
Se inscreva também no canal do Blog em: YouTube Thiengo.
Vídeos
A seguir os vídeos mostrando o passo a passo de desenvolvimento desta primeira parte do projeto Android de m-commerce:
O projeto também pode ser seguido pelo GitHub dele em: https://github.com/viniciusthiengo/blueshoes-kotlin-android.
Conclusão
Neste artigo, segundo da série sobre o mobile-commerce Android BlueShoes, desenvolvemos não a parte mais simples, mas a essencial para que as partes simples e complexas sejam posteriormente desenvolvidas.
Como desenvolvedor Android, a mensagem importante deste artigo é que o menu gaveta pode ter qualquer configuração, somos livres para utilizar a interface que quisermos como layout de gaveta ou layout principal.
Caso você tenha dúvidas ou dicas para este projeto, deixe logo abaixo nos comentários.
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
Criação de uma gaveta de navegação
Colocando Telas de Introdução em Seu Aplicativo Android - Configurações de estilo
Unleash functional power on Android (I): Kotlin lambdas
Mipmaps vs. drawable folders [duplicate] - Resposta de Joel e Rajesh
android providing different drawable resources for orientations - Resposta de blejzz
How to achieve ripple animation using support library? - Resposta de Amintabar
Storing R.id.elements in XML resources
Comentários Facebook