Aplicando técnicas de rolagem do Material Design no Android

Como aplicar as técnicas de rolagem do Material Design usando a biblioteca de suporte Android Design Support Library


Acredito que não exista algum desenvolvedor Android que ainda não ouviu falar sobre o Material Design, uma vez que ele se tornou oficialmente um padrão de design.

Não era tão simples aplicar o Material Design em aplicações Android porque muitos componentes de interface do usuário (UI) como o Floating Action Button (FAB) não estavam disponíveis publicamente. Algumas das opções eram implementar o seu próprio componente ou usar alguma biblioteca de terceiro publicada por algum desenvolvedor independente Android.

Durante o Google I/O 2015, a Google anunciou uma importante biblioteca de suporte chamada Android Design Support Library que fornece uma série de componentes de UI do Material Design em uma única biblioteca como: Floating Labels para entrada de texto, Floating Action Buttons, Snackbars, NavigationView, entre outros.

Através desta biblioteca tornou-se mais simples a aplicação de padrões especificados pelo Material Design.


Técnicas de rolagem

De acordo com as especificações do Material Design, a barra superior de uma aplicação (chamada de App Bar) é dividida em quatro grandes blocos que compôe a estrutura de rolagem:

Barra de status

Barra de ferramentas

Barra de abas ou barra de pesquisa

Espaço flexível: Utilizado para ter um tamanho dinâmico para imagens ou a para a App Bar estendida

App Bar padrão

Há duas maneiras de rolar uma Tool Bar:

Tool Bar rola quando o usuário faz rolagem na tela
  • Ela pode rolar pra fora da tela quando o usuário rolar o conteúdo pra cima e retornar pra tela quando o usuário rolar o conteúdo todo pra baixo.
  • Ou pode ficar fixa no topo com o conteúdo rolando abaixo dela.

Abas

A App Bar também pode incluir uma Tab Bar com um dos seguintes comportamentos:

Tool Bar rola enquanto a Tab Bar fica fixa na tela
  • A Tab Bar fica fixa enquanto a Tool Bar rola pra fora da tela.
  • As duas barras ficam fixas enquanto o conteúdo é rolado embaixo.
  • Ou ambas rolam pra cima junto com o conteúdo. Porém a Tab Bar retorna à tela assim que o usuário rolar pra baixo e a Tool Bar só retorna quando o conteúdo for rolado completamente pra baixo.

Espaço flexível

Uma vez que a App Bar é flexível, ela pode estender para acomodar títulos maiores ou imagens.

Há duas formas de exibição:

Espaço flexível com a Tool Bar
  • O espaço flexível é recolhido exibindo apenas a Tool Bar. Quando o usuário rolar para o topo do conteúdo, o espaço flexível e o título são expandidos novamente.
  • A App Bar inteira é rolada pra fora da tela. Quando o usuário rolar pra baixo, a Tool Bar retorna recolhida. Somente quando usuário rolar o conteúdo todo pra baixo é que o espaço flexível e o título são expandidos novamente.

Espaço flexível com imagem

Usa o espaço flexível para acomodar imagens na App Bar.

Espaço flexível com Tool Bar e imagem

Neste exemplo, quando o usuário rolar a tela, o conteúdo “empurra” a imagem e vai diminuindo sua área de exibição, recolhendo o espaço flexível. No final da transformação, a imagem fica com a cor primária da aplicação.

Este padrão já pode ser observado em algumas aplicações que foram repaginadas com o Material Design, como o WhatsApp por exemplo.


Configuração inicial

Antes de utilizar esta biblioteca de suporte é preciso atualizar pelo SDK Manager a versão mais recente da Android Design Support Library. Em seguida, adicionar a dependência de compilação no gradle.

dependencies {
compile 'com.android.support:design:22.2.1'
}

Como a Android Design Support Library depende das bibliotecas de suporte v4 e AppCompat, elas são incluídas automaticamente quando você adiciona a dependência da biblioteca de Design.


CoordinatorLayout

Fornece uma camada adicional de controle sobre os eventos de toque entre suas Views filhas. Isso é utilizado por muitos componentes da biblioteca de suporte.

O CoordinatorLayout também fornece às suas Views os atributos layout_anchor e layout_anchorGravity, que podem ser utilizados para colocar alguma View “flutuando” relativamente a outra.

FloatingActionButton ancorado no canto inferior direito de um CoordinatorLayout
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
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.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/coordinator_layout"
app:layout_anchorGravity="right|bottom"/>
</android.support.design.widget.CoordinatorLayout>

AppBarLayout

Com um AppBarLayout você pode fazer a sua Toolbar reagir ao evento de rolagem de uma outra View que tenha definido um ScrollingViewBehavior. Com isto podemos, por exemplo, adaptar seu layout baseado em diferentes eventos de rolagem, permitindo alterar a aparência de suas Views quando o usuário rolar o conteúdo na tela.

AppBarLayout contendo uma Tool Bar
<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.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
   <android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
      <android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways"/>
   </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

Agora, quando o usuário rolar o RecyclerView, o AppBarLayout pode responder a esses eventos usando as flags de rolagem de suas Views filhas para controlar como elas serão exibidas ou escondidas na tela. As opções de flag são:

  • scroll: essa flag deve ser definida em todas as Views que precisam ser roladas na tela. Para Views que não usam esta flag, elas permanecerão fixa no topo da tela.
  • enterAlways: essa flag garante que qualquer rolagem para baixo fará com que esta View seja exibida, utilizando o conceito de ‘retorno rápido’.
  • enterAlwaysCollapsed: Quando a View tem definido o atributo minHeight e você usa esta flag, a View só exibirá com seu tamanho mínimo, somente sendo exibida por completo quando a View de rolagem tiver sido rolada até o topo.
  • exitUntilCollapsed: Essa flag faz a View recolher para fora da tela até que ela atinja seu tamanho mínimo (minHeight) antes de sair.

AppBarLayout com abas

Com o TabLayout você pode ter abas fixas (onde a largura da View é dividida igualmente entre todas as abas), assim como abas com rolagem (onde as abas não tem uma largura uniforme e podem rolar horizontalmente). As abas podem ser adicionadas programaticamente:

TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));

Mas se você estiver usando um ViewPager para paginação horizontal entre as abas, você pode criar os conteúdos das abas através do método getPageTitle() do PagerAdapter e então conectá-los usando o setupWithViewPager(). Isso garante que qualquer evento de seleção de aba irá atualizar o ViewPager e uma mudança de página atualizará a aba selecionada.

TabLayout conectada a um ViewPager
<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.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
   <android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
       <android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways"/>
       <android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
   </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewPager viewPager = (ViewPager)
findViewById(R.id.view_pager);
viewPager.setAdapter(new
PagerAdapter(getSupportFragmentManager()));
// Configura o TabLayout com o ViewPager
TabLayout tabLayout = (TabLayout)
findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
}

CollapsingToolbarLayout

Quando adicionamos uma Toolbar diretamente em um AppBarLayout podemos acessar as flags de rolagem: scroll, enterAlways, enterAlwaysCollapsed e exitUntilCollapsed mas não podemos controlar detalhes de como as diferentes Views filhas reagirão ao sair da tela. Para isto, podemos utilizar o CollapsingToolbarLayout:

CollapsingToolbarLayout padrão

Usamos o app:layout_collapseMode=”pin” para garantir que a Toolbar permaneça fixa no topo da tela enquanto a View é recolhida.

<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.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
   <android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="192dp">
       <android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
           <android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
       </android.support.design.widget.CollapsingToolbarLayout>
   </android.support.design.widget.AppBarLayout>
   <android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="right|bottom"/>
</android.support.design.widget.CoordinatorLayout>

Quando usamos o CollapsingToolbarLayout e Toolbar juntos, o título automaticamente aparecerá maior quando o layout for totalmente visível e animará para o tamanho padrão quando for recolhido. Mas para isto, devemos chamar o método setTitle() do CollapsingToolbarLayout, em vez do método da Toolbar.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
CollapsingToolbarLayout collapsingToolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbarLayout.setTitle(getTitle());
}

Além disso, podemos usar o app:layout_collapseMode=”parallax” (e opcionalmente o app:layout_collapseParallaxMultiplier=”0.7" para definir o multiplicador do efeito) para ativar o efeito de rolagem parallax (que neste exemplo faz o ImageView rolar de maneira mais suave que a rolagem do usuário na tela, dando um efeito de profundidade).

CollapsingToolbarLayout com imagem usando a rolagem parallax

Usando o app:contentScrim=”?attr/colorPrimary” aplica uma transição da imagem do ImageView para a cor primária do tema quando a View for recolhida.

<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
   <ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/desert"
app:layout_collapseMode="parallax"/>
   <android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>

CoordinatorLayout e Views customizadas

O CoordinatorLayout não tem conhecimento de como um FloatingActionButton ou um AppBarLayout funcionam. Ele apenas fornece uma API adicional, o CoordinatorLayout.Behavior, que permite à suas Views terem um melhor controle dos eventos de toque e gestos, assim como declarar dependências entre si e receber callbacks via onDependentViewChanged().

O FloatingActionButton já implementa o CoordinatorLayout.Behavior com o comportamento esperado quando é usada como filha de um CoordinatorLayout.

Uma View customizada que rola pelo lado inferior quando o usuário faz rolagem da tela
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
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">

...
   <com.example.andremion.designsupportlibrary.CustomView
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
...
app:layout_anchor="@id/coordinator_layout"
app:layout_anchorGravity="bottom"/>
</android.support.design.widget.CoordinatorLayout>

As Views podem declarar um Behavior padrão usando a annotation CoordinatorLayout.DefaultBehavior na classe, ou definir no arquivo de layout através do app:layout_behavior=”Nome completo da sua classe que implementa o CoordinatorLayout.Behavior”, exemplo: app:layout_behavior=”com.example.andremion.designsupportlibrary.CustomView$Behavior”. Com isto é possível integrar qualquer View com o CoordinatorLayout.

@CoordinatorLayout.DefaultBehavior(CustomView.Behavior.class)
public class CustomView extends TextView {
   public CustomView(Context context) {
super(context);
}
   public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
   public CustomView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
   public static class Behavior extends 
CoordinatorLayout.Behavior<CustomView> {

@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
CustomView child, View dependency) {
return dependency instanceof AppBarLayout;
}
       @Override
public boolean onDependentViewChanged(
CoordinatorLayout parent, CustomView child,
View dependency) {

if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout =
(AppBarLayout) dependency;

Rect rect = new Rect();
appBarLayout.getHitRect(rect);

if (rect.bottom <= ViewCompat.getMinimumHeight(
appBarLayout)) {
hide(child, parent.getHeight());
} else {
show(child,
parent.getHeight() - child.getHeight());
}
               return true;
}
           return false;
}
       private void show(View view, float y) {
view.animate().y(y);
}
       private void hide(View view, float y) {
view.animate().y(y);
}
}
}

Conclusão

A biblioteca de Design, AppCompat, e todas as outras do Android Support são importantes ferramentas que fornecem o necessário para construir um moderno e bonito aplicativo Android sem precisar criar tudo do zero.

Esta biblioteca ajuda a aplicar vários padrões do Material Design, como por exemplo, as técnicas de rolagem.


O código completo dos exemplos mostrados aqui estão disponíveis em https://github.com/andremion/Scrolling-techniques-using-Android-Design-Support-Library