Material Design: Aprimorando seus aplicativos Android

Este artigo demonstra como utilizar a biblioteca de suporte Design para criar uma aplicação com uma aparência moderna e que segue as recomendações do Material Design.

Este artigo demonstra como utilizar a biblioteca de suporte Design para criar uma aplicação com uma aparência moderna e que segue as recomendações do Material Design. Começamos com uma explicação do que é o Material Design e os principais componentes que a biblioteca de design provê. Você também aprenderá como integrar essa biblioteca em seu projeto, como adicionar um Navigation Drawer, Floating Action Button ou Collapsing Toolbar em sua app e, finalmente, como integrar esses componentes para ter uma aplicação que salte aos olhos de seus usuários.

A biblioteca de suporte denominada Design faz parte de um conjunto de recursos disponibilizado pelo Google para o Android que objetiva facilitar o uso de elementos introduzidos na versão Lollipop baseados no Material Design. Ela provê implementações de elementos como a Snackbar e o Floating Action Button (FAB).

Material Design é um conjunto de recomendações e especificações para guiar o design de seu produto para que ele tenha uma linguagem visual com elementos que simulam um “material”. O “material” é uma metáfora para algo que possui uma superfície, bordas e profundidade, mas também é flexível sem quebrar as leis da física. Os fundamentos do design “impresso”, como tipografia, espaço, escala, cor e imagens, devem guiar o aspecto visual do produto. Esses elementos devem criar uma hierarquia, ter um sentido e atrair o foco do usuário para o que é importante.

A movimentação dos objetos, a forma como eles se movimentam e as animações são aspectos muito importantes no material design. O movimento deve ser fluído e contínuo, sem mudanças súbitas e de uma forma coordenada com os outros objetos da tela. Objetos não devem aparecer ou desaparecer subitamente e devem sempre reagir às ações do usuário.

Material Design no Android

Apesar do Material Design ser genérico e se aplicar a qualquer tecnologia, ele teve sua origem e se popularizou no Android. Com o lançamento da versão Lollipop, a Google remodelou a interface gráfica de sua plataforma para implementar os conceitos do Material Design e também atualizou suas principais aplicações para seguir essa nova linguagem de design.

Novos elementos e animações foram introduzidos na API do Android para facilitar a implementação, porém eles só estavam disponíveis no Lollipop, sem uma implementação compatível com versões anteriores do sistema operacional. Devido à dificuldade de implementar os novos elementos de uma forma retrocompatível, a adesão ao Material Design foi muito lenta no início. Para acelerar esse processo e tornar a implementação consistente entre as versões do Android, o Google lançou em 2015 a biblioteca de suporte Design.



A biblioteca de suporte

A biblioteca de suporte Design foi criada para facilitar a criação de aplicações que utilizam componentes e comportamentos característicos do Material Design de uma forma compatível com versões do Android 2.1 e superiores (API 7+). Com ela podemos usar componentes como Snackbar ou FAB sem precisar se preocupar (muito) com usuários em versões mais antigas do Android pois todo o trabalho já foi feito pela Google.

A seguir iremos apresentar quais componentes são disponibilizados e quando utilizar cada um deles. Depois dessa apresentação iremos colocar a mão na massa e criar uma aplicação de exemplo que demonstra e integra todos os componentes.

Floating Action Button

O famoso FAB (Figura 1) é um dos elementos mais característicos do Lollipop e do Material Design. Ele deve ser utilizado quando há uma ação primária com alta chance de ser utilizada em uma tela. É um botão redondo “flutuante” que faz uma sombra no “material” abaixo.

Figura 1. Floating Action Button

Snackbar

A Snackbar (Figura 2) é uma evolução do Toast e tem algumas funções a mais. Além de mostrar uma notificação rapidamente na tela sobre os outros componentes, ela pode ser dispensada com um swipe e pode também ter até dois botões com ações.

Figura 2. Snackbar

Label flutuante para caixa de texto

O TextInputLayout (Figura 3) é usado em conjunto com um EditText para mostrar um hint quando a caixa de texto não contém o foco e um label acima da caixa de texto quando ela ganha o foco. Isso faz com que o usuário sempre veja para que serve aquela caixa de texto e não perca o contexto quando ela é ativada. A transição de hint para label acontece com uma animação.

Figura 3. TextInputLayout

Navigation View

Esse componente (Figura 4) facilita muito a implementação da interface de navegação, que é acessada através do botão no canto esquerdo da action bar. Os itens de navegação são declarados usando o mesmo formato de um menu e podem conter grupos e um cabeçalho.

Figura 4. Navigation View

Abas

Abas (Figura 5) não são novas no Android, mas esse componente da biblioteca de Design torna muito simples a implementação de um layout com abas que possuem um tamanho fixo ou indeterminado com rolagem horizontal.

Figura 5. Abas

Coordinator layout

O CoordinatorLayout é um ViewGroup que provê um maior controle e coordenação das views em um layout. Ele é utilizado por outros componentes da biblioteca para coordenar a movimentação de componentes. Por exemplo, um FAB que está inserido em um CoordinatorLayout irá se movimentar para cima quando um Snackbar for exibido na tela para que um não sobreponha o outro (Figura 6).

Figura 6. Coordinator layout

AppBarLayout

Esse componente é um LinearLayout vertical que implementa comportamentos de scrolling em conjunto com a toolbar. Ele depende do CoordinatorLayout para a maioria de suas funções e deve ser utilizado como um filho direto dele. Os filhos desse layout devem definir o comportamento do scroll com a propriedade app:layout_scrollFlags.

Collapsing toolbar

Esse componente é utilizado em conjunto com o AppBarLayout e uma toolbar e implementa um cabeçalho, normalmente contendo uma imagem (Figura 7), que diminui de tamanho quando o usuário faz scroll até sobrar somente uma toolbar.

Figura 7. Collapsing toolbar

Construindo uma aplicação com a biblioteca de Design

Para demonstrar a capacidade da biblioteca de Design em simplificar a implementação de uma app que utiliza o Material Design e é retrocompatível, vamos criar uma aplicação “bloco de notas”, onde você poderá adicionar notas com um título, uma imagem e um texto livre. Você também poderá marcar suas notas preferidas como favoritas e elas serão exibidas em uma lista de favoritos.

Nosso layout irá incluir uma tela principal com duas abas, a primeira com uma lista de notas em ordem de criação e a segunda com as notas marcadas como favoritas. Também teremos um FAB no canto inferior direito para criar novas notas. Ao tocar uma nota, a tela de detalhes dessa nota será aberta, onde será exibida a imagem da nota em uma Collapsing Toolbar, o título e o corpo da nota. Também teremos um FAB para marcar essa nota como favorita.

Ao tocar o FAB na tela principal, o usuário será levado para a tela de criação de notas onde serão exibidos TextInputLayouts para a entrada dos dados. Um botão para salvar a nota será exibido embaixo dos campos para entrada de dados e, quando tocado, irá salvar a nota e retornar para a tela principal. Ao fazer isso, será exibida uma Snackbar confirmando o salvamento e contendo uma ação “Favoritar”, que poderá ser utilizada para adicionar a nota rapidamente aos favoritos. A exibição da Snackbar deverá “empurrar” o FAB para cima, pois ele estará inserido em um CoordinatorLayout.

Além disso, o usuário poderá acessar o menu de navegação através do botão home no canto esquerdo da toolbar na tela principal. Esse menu de navegação irá exibir um cabeçalho com o nome do usuário e duas opções de navegação: Principal e Preferências. Confira na Figura 8 o layout final das telas.

Figura 8. Telas da aplicação

Criando o projeto

Vamos começar nossa aplicação criando um novo projeto com uma Activity vazia no Android Studio. Isso irá criar o esqueleto de nossa aplicação. A primeira alteração que faremos será adicionar a biblioteca de design como uma dependência no arquivo de build do projeto, como na Listagem 1.

Nota: Existem outras opções no menu de criação de projeto onde podem ser criadas aplicações que já incluem automaticamente um menu de navegação e um FAB. Nós vamos utilizar uma aplicação vazia para fins didáticos e para que você saiba como adicionar os componentes em uma aplicação já existente.
[...] dependencies { compile "com.android.support:appcompat-v7:23.1.1" compile "com.android.support:design:23.1.1" compile "com.android.support:support-v4:23.1.1" [...] }
Listagem 1. Adicionando a dependência no arquivo de build

A segunda coisa que faremos será criar um arquivo de estilos onde definiremos o tema e as cores principais de nossa aplicação. Essas propriedades fazem parte da biblioteca appcompate são essenciais para que nossa aplicação tenha uma boa aparência. A biblioteca appcompat faz parte do conjunto de bibliotecas de suporte e provê funcionalidades e componentes adicionados nas versões Ice Cream Sandwich e superiores para versões anteriores do Android. Uma das principais funcionalidades que ela provê é colorir automaticamente os componentes do Android com as cores de sua aplicação. Para que a biblioteca consiga fazer isso, iremos alterar o arquivo styles.xml dentro da pasta res/values para definir essas cores, conforme apresentado na Listagem 2.

<resources> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
Listagem 2. Definindo o tema da aplicação

Com esse arquivo de estilos definimos o tema principal de nossa aplicação (Theme.AppCompat.Light.DarkActionBar), suas cores principais (colorPrimary, colorPrimaryDark e colorAccent), que não usaremos a action bar do sistema (windowActionBar) e que não usaremos um título do sistema (windowNoTitle). Os estilos AppTheme.AppBarOverlay e AppTheme.PopupOverlay serão utilizados nos passos futuros para definir as cores de nossa toolbar e do menu de overlay.

Criando a tela principal com o menu de navegação

Agora podemos começar a utilizar os componentes da biblioteca, e o primeiro que vamos adicionar é o menu de navegação. Iremos substituir o conteúdo do arquivo de layout activity_main.xml criado juntamente com o projeto por um DrawerLayout contendo um NavigationView, como pode ser visto na Listagem 3. O DrawerLayout terá a função de "esconder" a NavigationView no canto esquerdo e permitir que ela seja exibida com um swipe a partir da borda esquerda.

<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"> <!-- incluiremos aqui o layout com toolbar no futuro --> <include layout="@layout/app_bar_layout" 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>
Listagem 3. Tela principal com menu de navegação

Configuramos o DrawerLayout com a flag fitsSystemWindows="true", que fará com que esse componente não entre em conflito com a status bar ou a navigation bar, ajustando seu padding automaticamente. Também configuramos a posição do nosso menu com tools:openDrawer="start", que o colocará no canto esquerdo para línguas escritas da esquerda para direita e no canto direto caso contrário.

Também incluímos o app_bar_layout,que conterá nossa toolbar e será definido nas próximas etapas, e o componente NavigationView, que possui as propriedades fitsSystemWindows="true", explicada anteriormente,e android:layout_gravity="start",que posicionará os itens do menu da mesma maneira que o DrawerLayout. Além disso, temos duas propriedades que precisamos configurar: app:headerLayout, onde definiremos o layout do cabeçalho, e app:menu, onde definiremos os itens de navegação.

Vamos começar pelo mais simples, o menu. Para isso, criamos um novo diretório na pasta res chamado menu e, dentro desse diretório, criamos nosso arquivo activity_main_drawer.xml. Dentro desse arquivo teremos um grupo, em que somente um dos itens poderá estar selecionado, e nossos dois itens de navegação. O resultado final pode ser visto na Listagem 4.

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_principal" android:icon="@android:drawable/ic_menu_agenda" android:title="@string/nav_principal" /> <item android:id="@+id/nav_preferencias" android:icon="@android:drawable/ic_menu_preferences" android:title="@string/nav_preferencias" /> </group> </menu>
Listagem 4. Menu de navegação

A seguir faremos com que nossa Activity receba os eventos de navegação. Para isso, faremos com que ela implemente a interface NavigationView.OnNavigationItemSelectedListener e, quando receber um evento de navegação, crie e exiba na tela o Fragment correspondente àquele item. Também iremos inicializar a Toolbar como nossa ActionBar e ligar o botão de home ao componente NavigationView. A Listagem 5 contém o código atual da MainActivity.

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); } @Override public boolean onNavigationItemSelected(MenuItem item) { // Trate os eventos de navegação aqui DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } }
Listagem 5. MainActivity implementando OnNavigationItemSelectedListener

O método setNavigationItemSelectedListener do componente NavigationView fará com que a MainActivity receba um evento quando o usuário selecionar um dos itens do menu. Esse evento chamará o método onNavigationItemSelected onde devemos tratar esse evento e fechar o menu de navegação com o método closeDrawer(GravityCompat.START). A constante GravityCompat.START é necessária para indicar qual das posições do DrawerLayout deverá ser fechada pois é possível adicionar um menu em mais de uma posição.

Para completar nossa implementação do menu de navegação, iremos criar o layout do cabeçalho. Para isso criamos um novo arquivo no diretório layout dentro de res/layout com o nome nav_header_main.xml. Esse arquivo definirá um layout simples com a cor primária da app como background, uma imagem de perfil e o nome do usuário, conforme apresentado na Listagem 6.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="160dp" android:background="@color/colorPrimary" android:padding="16dp" android:gravity="bottom" android:orientation="vertical"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@android:drawable/sym_def_app_icon" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" android:text="@string/nome_usuario" /> </LinearLayout>
Listagem 6. Cabeçalho do menu de navegação

Agora temos um menu de navegação que pode ser acessado com um swipe da borda esquerda da tela. Para adicionar mais itens ao menu, precisamos somente adicionar mais um item ao arquivo activity_main_drawer.xml e ele será automaticamente exibido. Porém, ainda precisamos definir o app_bar_layout que incluímos no layout principal da MainActivity, que terá uma toolbar com um botão para acessar o menu.

AppBarLayout

Para que possamos adicionar efeitos de scrolling à nossa toolbar, vamos criá-la dentro de um AppBarLayout no arquivo app_bar_layout.xml que incluímos no layout da activity. Como falamos anteriormente, o AppBarLayout deve estar contido em um CoordinatorLayout, como podemos ver na Listagem 7.

<android.support.design.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <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" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </android.support.design.widget.AppBarLayout> <!-- aqui será inserido nossos fragments do menu --> <FrameLayout android:id="@+id/activity_main_conteudo" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout>
Listagem 7. Toolbar dentro de AppBarLayout dentro de CoordinatorLayout

Além do AppBarLayout, temos um FrameLayout no qual nos próximos passos incluiremos o fragmento principal da aplicação. Agora que temos uma toolbar, podemos exibir o botão do menu de navegação utilizando um ActionBarDrawerToggle,que será exibido na Toolbar e acionará o menu de navegação quando tocado. As alterações na MainActivity estão destacadas na Listagem 8. Isso tornará nosso menu mais visível e de fácil acesso na tela principal. A seguir criaremos a tela principal com as abas de Notas e Favoritos.

protected void onCreate(Bundle savedInstanceState) { [...] Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState(); }
Listagem 8. Exibindo o botão do menu na toolbar
Nota: Fique atento aos imports dos componentes da biblioteca de suporte, pois alguns deles possuem o mesmo nome que os componentes nativos. Por exemplo, devemos importar a classe android.support.v7.widget.Toolbar da biblioteca de suporte ao invés da classe android.widget.Toolbar nativa do Android.

Abas

Nossas abas ficarão no fragmento principal, que será ativado quando o usuário abrir a aplicação ou quando selecionar o item “Principal” no menu de navegação. Criaremos um layout simples para esse fragmento com um LinearLayout contendo o TabLayout e um FrameLayout que exibirá o fragmento selecionado nas abas (“Notas” ou “Favoritos”).

O código Java para o fragmento também será muito simples, iremos criar as abas e adicionar um listener para quando elas forem selecionadas. As Listagens 9 e 10 mostram como fica o código do layout (res/layout/fragment_principal.xml) e Java (PrincipalFragment.java) do fragmento principal. O TabLayout possui duas propriedades principais que podem ser ajustadas para que se tenha o visual e comportamento desejado. O tabMode pode ser fixed, para mostrar todas as abas ao mesmo tempo, ou scrollable, que mostra uma lista rolável de abas. A primeira opção é indicada para quando se tem poucas abas, e a segunda para quando se tem muitas ou uma quantidade indefinida de abas. O parâmetro tabGravity define se as abas ocuparão todo o espaço disponível para elas (fill) ou se serão centralizadas (center).

<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="match_parent" android:orientation="vertical"> <android.support.design.widget.TabLayout android:id="@+id/fragment_principal_tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark" app:tabMode="fixed" app:tabGravity="fill"/> <FrameLayout android:id="@+id/fragment_principal_conteudo" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Listagem 9. Layout com abas

Note na Listagem 9 que, além do TabLayout, temos um FrameLayout (id/fragment_principal_conteudo) que deve conter o fragmento a ser exibido na aba selecionada. Com o fragmento principal criado, podemos alterar a MainActivity para exibi-lo quando a aplicação for iniciada e quando o item do menu for acionado.

Na Listagem 10 são criadas duas abas utilizando o método newTab da classe TabLayout e definimos o título de cada aba com o método setText. Através do método addTab as novas abas são adicionadas ao layout. Para que possamos exibir o fragmento que preencherá cada aba, adicionamos um listener para que esse fragmento seja criado e exibido quando o método onTabSelected for chamado.

public class PrincipalFragment extends Fragment { public static PrincipalFragment newInstance() { return new PrincipalFragment(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_principal, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); TabLayout tabLayout = (TabLayout) view.findViewById(R.id.fragment_principal_tab_layout); tabLayout.addTab(tabLayout.newTab().setText(R.string.notas)); tabLayout.addTab(tabLayout.newTab().setText(R.string.favoritos)); tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { // TODO: criar e exibir o fragmento selecionado } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); } }
Listagem 10. Criando abas e adicionando listener

A Listagem 11 destaca os pontos que precisam ser adicionados na MainActivity para que o fragmento principal seja exibido. O código adicionado ao método onCreate irá exibir o fragmento quando a Activity for iniciada sem um estado prévio. Já o código adicionado ao método onNavigationItemSelected irá exibir o fragmento quando o usuário o selecionar através do menu de navegação. Com essas alterações, nossa aplicação deve exibir duas abas quando iniciada. O próximo passo será exibir um FAB para que o usuário possa adicionar uma nota.

[...] @Override protected void onCreate(Bundle savedInstanceState) { [...] if (savedInstanceState == null) { navigationView.setCheckedItem(R.id.nav_principal); exibirFragmentPrincipal(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { [...] switch (item.getItemId()) { case R.id.nav_principal: exibirFragmentPrincipal(); break; case R.id.nav_preferencias: // TODO: criar e exibir fragment de preferências break; } [...] } private void exibirFragmentPrincipal() { Fragment novoFragment = PrincipalFragment.newInstance(); getSupportFragmentManager().beginTransaction() .replace(R.id.activity_main_conteudo, novoFragment) .commit(); }
Listagem 11. Exibindo o fragmento principal na MainActivity

FAB

Adicionaremos o FAB no app_bar_layout pois queremos que ele seja um filho direto do CoordinatorLayout para que reaja à exibição de uma Snackbar. Para isso, iremos alterar o layout e adicionar o FAB flutuando no canto inferior direito. No código Java da activity iremos adicionar um listener para o botão que irá iniciar a activity para adicionar uma nota. As alterações podem ser vistas nas Listagens 12 e 13. O próximo passo será implementar a activity para criação de notas (AdicionarNotaActivity.java), onde utilizaremos o componente TextInputLayout.

<android.support.design.widget.CoordinatorLayout [...] <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:layout_gravity="bottom|end" android:src="@android:drawable/ic_menu_edit" /> </android.support.design.widget.CoordinatorLayout>
Listagem 12. FAB no app_bar_layout
[...] protected void onCreate(Bundle savedInstanceState) { [...] FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, AdicionarNotaActivity.class); startActivityForResult(intent, 0); } }); }
Listagem 13. Adicionando listener no FAB
Nota: Sempre teste o layout de sua aplicação no maior número de versões do Android que você tenha acesso. Apesar das bibliotecas de suporte facilitarem muito a integração de funcionalidades antes restritas às novas versões, ainda existem inconsistências que precisam ser tratadas pelo desenvolvedor. No caso do FAB, as diferenças na implementação entre as versões fazem com que as margens sejam tratadas de forma diferente. Você provavelmente precisará definir um valor de margem para versões até Lollipop e outro valor de Lollipop em diante.

Editando texto com TextInputLayout

A activity para adição de notas é bem simples e consiste apenas de dois EditTexts envoltos por TextInputLayouts, para adicionar o título e o corpo da nota, e um botão para adicionar uma imagem, além de um AppBarLayout. Teremos também um botão para salvar e um para cancelar. O botão “Adicionar Imagem” foi adicionado somente para manter a lógica do programa, contudo sua lógica não foi implementada.

O código Java irá salvar os dados entrados pelo usuário e retornar à activity principal quando o usuário tocar o botão de salvar. Os detalhes de como armazenar as notas fogem do escopo desse artigo e não serão exibidos. As Listagens 14 e 15 mostram o arquivo de layout utilizando TextInputLayout e o código Java, respectivamente.

<android.support.design.widget.CoordinatorLayout 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="match_parent" android:fitsSystemWindows="true"> <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> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/activity_adicionar_nota_titulo" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text|textCapSentences" android:maxLines="1" android:hint="@string/titulo"/> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginTop="8dp"> <EditText android:id="@+id/activity_adicionar_nota_texto" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text|textCapSentences|textMultiLine" android:hint="@string/texto_da_nota"/> </android.support.design.widget.TextInputLayout> <Button android:id="@+id/button_adicionar_imagem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/adicionar_imagem"/> <Button android:id="@+id/button_salvar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/salvar"/> <Button android:id="@+id/button_adicionar_cancelar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/cancelar"/> </LinearLayout> </android.support.design.widget.CoordinatorLayout>
Listagem 14. Layout da activity para adição de notas com TextInputLayout (activity_adicionar_nota.xml)
public class AdicionarNotaActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_adicionar_nota); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); Button salvarButton = (Button) findViewById(R.id.button_salvar); Button cancelarButton = (Button) findViewById(R.id.button_adicionar_cancelar); salvarButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { salvarNota(); } }); cancelarButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { cancelarNota(); } }); } private void cancelarNota() { setResult(RESULT_CANCELED); finish(); } private void salvarNota() { salvarNotaNoRepositorio(); recolherTeclado(); setResult(RESULT_OK); finish(); } [...] }
Listagem 15. A classe AdicionarNotaActivity

Os EditTexts exibirão o hint quando não tiverem o foco e passarão a exibir um label acima da caixa de texto quando ganharem o foco. Caso sua lógica de negócio necessite validar esses campos, você pode usar o método setError() para adicionar uma mensagem de erro à caixa de texto. Quando o usuário tocar o botão de salvar, a activity principal receberá a notificação do resultado da activity de adição de notas e exibirá um Snackbar caso a nota tenha sido salva, como na Listagem 16.

O componente Snackbar tem uma interface similar ao Toast,utilizando o padrão de projeto builder para construir e configurar uma snackbar e, finalmente, o método show() para exibi-la. A principal diferença do Toast é a possibilidade de configurar uma ação com o método setAction, como fizemos na Listagem 16. A seguir vamos implementar a tela de detalhe de uma nota.

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { Snackbar.make(findViewById(R.id.activity_main_conteudo), R.string.nota_salva, Snackbar.LENGTH_LONG) .setAction(R.string.favoritar, new View.OnClickListener() { @Override public void onClick(View v) { // TODO: favoritar nota na base de dados } }) .show(); } }
Listagem 16. Exibindo o Snackbar

Tela de detalhe

Em cada uma das abas da tela principal será exibida uma lista com as notas cadastradas no sistema. A implementação dessas listas não utiliza os componentes da biblioteca de design e não será abordada neste artigo. Quando o usuário tocar em um dos itens dessa lista, a tela DetalheActivity será exibida com a imagem da nota de forma destacada na toolbar. Essa imagem será acompanhada do título e de um FAB para adicionar a nota aos favoritos. O texto preencherá o restante da tela.

Como podemos ver na Listagem 17, temos um CoordinatorLayout como raiz do layout. Temos também um CollapsingToolbarLayout dentro de um AppBarLayout, que será o recipiente da imagem e do título da nota. Para que nosso texto se desloque na tela, ele é envolto por uma NestedScrollView (que possibilita a definição de um ScrollView dentro do outro). Por fim, temos um FAB ancorado no canto inferior direito do AppBarLayout.

Vamos analisar alguns dos parâmetros interessantes desse layout, começando pelo layout_collapseMode presente na ImageView e na Toolbar. Esse parâmetro define o que acontecerá com essa view quando a tela se movimentar. Nesse caso, definimos a imagem como parallax, o que fará com que ela se movimente junto com a tela com um efeito de paralaxe, permanecendo centralizada enquanto o tamanho do AppBarLayout diminui. A toolbar é definida como pin, para que ela permaneça fixa no topo da tela.

Outro parâmetro interessante é o layout_scrollFlags presente no CollapsingToolbarLayout, que recebe duas flags: scroll e exitUntilCollapsed. A primeira simplesmente declara que esse componente irá reagir aos eventos de scroll e precisa estar presente para as outras flags terem efeito. A segunda determina que a view role na tela até o seu tamanho mínimo, que nesse caso é o tamanho da toolbar. Existem mais três flags possíveis:

Nota: Não esqueça de incluir as activities AdicionarNotaActivity e DetalheActivity no arquivo de manifest do seu projeto.
<android.support.design.widget.CoordinatorLayout 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="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="180dp" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/detalhe_imagem" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:id="@+id/detalhe_texto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/text_margin" tools:text="@string/large_text" /> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/btn_star_big_off" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end" /> </android.support.design.widget.CoordinatorLayout>
Listagem 17. A tela de detalhe (activity_detalhe.xml)

Na DetalheActivity iremos recuperar a nota de nosso repositório de notas e aplicar seu conteúdo ao layout. A imagem da nota será inserida na ImageView dentro da CollapsingToolbar e o texto será inserido como título. Também adicionamos um listener ao FAB para favoritar a nota. O código da DetalheActivity pode ser visto na Listagem 18.

public class DetalheActivity extends AppCompatActivity { private static final String EXTRA_POSITION = "POSITION"; public static Intent getIntent(Context context, int position) { Intent intent = new Intent(context, DetalheActivity.class); intent.putExtra(EXTRA_POSITION, position); return intent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detalhe); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // TODO: favoritar nota na base de dados Snackbar.make(view, "Nota favoritada", Snackbar.LENGTH_LONG).show(); } }); preencherDetalhes(getIntent().getIntExtra(EXTRA_POSITION, 0)); } private void preencherDetalhes(int position) { Nota nota = recuperarNotaDaBase(position); ImageView imagemImageView = (ImageView) findViewById(R.id.detalhe_imagem); TextView textoTextView = (TextView) findViewById(R.id.detalhe_texto); textoTextView.setText(nota.getTexto()); imagemImageView.setImageDrawable(nota.getImagem()); CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); collapsingToolbar.setTitle(nota.getTitulo()); } [...] }
Listagem 18. Exibindo os detalhes da nota

Houve um tempo em que criar aplicações modernas que suportam versões ultrapassadas do Android (mas ainda com uma grande quantidade de usuários) era uma grande dor de cabeça. Problemas com mudança de comportamento e indisponibilidade de APIs tornavam muito difícil a implementação de novas funcionalidades em versões antigas. As bibliotecas de suporte foram criadas para consertar – ou pelo menos aliviar – esse problema, e a biblioteca de Design torna possível a utilização de componentes do material design de uma forma fácil e retrocompatível.

Ainda é necessário tomar cuidado com algumas diferenças entre as várias versões suportadas, mas o esforço vale muito a pena, pois sua app poderá entregar ao usuário uma experiência moderna mesmo em uma plataforma ultrapassada.


Links Úteis


Saiba mais sobre Android ;)

  • Linguagem Java: Neste Guia de Referência você encontrará todo o conteúdo que precisa para começar a programar com a linguagem Java, a sua caixa de ferramentas base para criar aplicações com Java.
  • Guia Completa de Android: O Android é a plataforma oficial para desenvolvimento de aplicativos mobile do Google. Neste guia você encontrará artigos, vídeos e cursos para aprender tudo sobre esse SDK, que domina o mundo dos dispositivos móveis.
  • Carreira Programador Java: Nesse Guia de Referência você encontrará o conteúdo que precisa para iniciar seus estudos sobre a tecnologia Java, base para o desenvolvimento de aplicações desktop, web e mobile/embarcadas.

Artigos relacionados