quarta-feira, 19 de junho de 2013

SlidingPaneLayout: um menu lateral estilo Facebook

Olá povo,

Uma das coisas boas de mexer a muito tempo com uma tecnologia, é que as pessoas sempre perguntam a você, mas obviamente, nem sempre você sabe a resposta. Meu amigo Rodrigo Jardim (a.k.a. Praieiro), me perguntou se eu conhecia o SlidingPaneLayout, e eu nunca tinha visto o dito cujo. Mas ele permite implementarmos um padrão de interface gráfica amplamente utilizada nas aplicações Android: menus laterais. Abaixo, coloquei um screenshot de duas "pequenas" aplicações que usam essa abordagem.


Como podemos notar, o Facebook e o GMail são dois bons exemplos de grandes aplicações que usam essa abordagem. Nesse post vou mostrar como dar os primeiros passos para construir um menu desse tipo. Vamos começar pelo arquivo de layout da aplicação, mostrado abaixo.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/sliding_pane_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <!-- Menu Lateral -->
  <ListView
    android:id="@+id/left_pane"
    android:layout_width="280dp"
    android:layout_height="match_parent"
    android:layout_gravity="left" />
    
  <!-- Conteúdo da tela -->
  <RelativeLayout
    android:id="@+id/rightPane"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff333333" >

    <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentTop="true"
      android:text="MENU" 
      android:onClick="abrirMenu"/>

    <ImageView
      android:id="@+id/imageView1"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:src="@drawable/ic_launcher" />

  </RelativeLayout>

</android.support.v4.widget.SlidingPaneLayout>
No SlidingPanelLayout temos apenas duas partes, a primeira (uma ListView) será o menu da aplicação. Enquanto que a segunda, será o conteúdo da tela em si. Abaixo temos o código da Activity.
public class MainActivity extends FragmentActivity 
  implements OnItemClickListener, PanelSlideListener {

  private SlidingPaneLayout mSlidingLayout;
  private ListView mList;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Colocando a Activity em tela cheia (opcional)
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    setContentView(R.layout.activity_main);
  

    mSlidingLayout = (SlidingPaneLayout) 
      findViewById(R.id.sliding_pane_layout);
    mSlidingLayout.setPanelSlideListener(this);
  
    String[] opcoes = new String[] { 
      "Opção 1", "Opção 2", "Opção 3",
      "Opção 4", "Opção 5" };

    mList = (ListView) findViewById(R.id.left_pane);
    mList.setAdapter(new ArrayAdapter<String>(
      this, 
      android.R.layout.simple_list_item_1, 
      opcoes));
    mList.setOnItemClickListener(this);
  }

  // Evento de clique do botão
  public void abrirMenu(View v){
    // Se estive aberto, feche. Senão abra.
    if (mSlidingLayout.isOpen()){
      mSlidingLayout.closePane();
    } else {
      mSlidingLayout.openPane();
    }
  }

  @Override
  public void onItemClick(AdapterView<?> adapterView, 
    View view, int position, long id) {
    // TODO Tratar opções de Menu (ListView) aqui!
  }

  @Override
  public void onPanelClosed(View arg0) {
    // TODO Ao fechar o painel
  }

  @Override
  public void onPanelOpened(View arg0) {
    // TODO Ao abrir o painel
  }

  @Override
  public void onPanelSlide(View arg0, float arg1) {
    // TODO Enquanto o painel desliza
  }
}
O código acima é bem simples e está comentado.
O mais legal desse componente é que ao deslizar o dedo sobre a tela da esquerda para direita, o menu exibido (da direita para esquerda, ele fecha). Ou seja, o botão acima é opcional, mas indicado, como vemos nas aplicações em geral (como a do Facebook e Gmail) para facilitar a visualização do usuário.
Abaixo podemos ver nossa aplicações em execução com o menu lateral aberto.

É isso pessoal! Temos um menu no padrão de aplicações profissionais. Vale salientar que esse componente funciona em todas as versões do Android.

EDITADO em 29/11/2013

Várias pessoas me perguntaram (por email.... deixem os comentários aqui povo!) o que fazer com o clique do item da lista. O mais legal aqui é usar Fragments e sua stack. Ou seja, criar uma pilha de Fragments como o Android faz com as Activities. Ou seja, ao invés de irmos chamando Activities, chamamos Fragments e vamos empilhando-os no layout da direita.
Vamos ao exemplo... Crie a classe MeuFragment conforme abaixo:
public class MeuFragment extends Fragment {

  public static MeuFragment newInstance(String s){
    Bundle args = new Bundle();
    args.putString("texto", s);
  
    MeuFragment f = new MeuFragment();
    f.setArguments(args);
    return f;
  }
 
  @Override
  public View onCreateView(LayoutInflater inflater, 
    ViewGroup container, Bundle savedInstanceState) {

    View layout = inflater.inflate(
      R.layout.meu_fragment, container, false);

    TextView txt = (TextView)
      layout.findViewById(R.id.textView1);

    txt.setText(getArguments().getString("texto"));
    return layout;
  }
}
O arquivo de layout do Fragment acima, só tem um RelativeLayout com um TextView centralizado (nada de mais, vocês conseguem :)
Agora, no clique de cada um dos itens, instanciamos esse fragment passando o texto da opção clicada.
@Override
public void onItemClick(AdapterView<?> adapterView, 
  View view, int position, long id) {

  String opcao = (String)
    mList.getAdapter().getItem(position);
  
  MeuFragment f = MeuFragment.newInstance(opcao);
  
  FragmentManager fm = getSupportFragmentManager();
  // Opcional: isso removerá o fragment anterior 
  // da pilha.
  fm.popBackStack(); 
  
  fm.beginTransaction()
    .replace(R.id.rightPane, f, "frag1")
    .addToBackStack(null)
    .commit();  
}
O método addToBackStack vai adicionar o Fragment a uma pilha, dessa forma, ao clicar no botão back do aparelho, o fragment será removido automaticamente e anterior será exibido.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: http://androidtrainningcenter.blogspot.com.br/2013/06/slidingpanelayout-android-making.html

18 comentários:

Rodrigo Jardim disse...

É isso ai Galuber...ficou muito legal!

Anônimo disse...

Sensasional!
Esse glauber é um arrombado dos cavalos mesmo!

Nelson Glauber disse...

Pode ser dos póneis, que são menores? :)

Anônimo disse...

Glauber! Obrigado pelo código!

Uma dúvida, nas opções do menu, eu tenho que usar a posição, certo? teria como você postar um exemplo usando esse menu, pra que a opção 5 seja o botão "sair" da aplicação? Seria de muita ajuda! Obrigado!

Nelson Glauber disse...

Oi Julio,

Não sei se entendi a dúvida, mas seria basicamente no onItemClick...
// Índice 4 = Opção 5
if (position == 4) finish();

4br4ç05,
nglauber

Anônimo disse...

Ola muito bom amigo seu tutorial antes eu usava um framework mais esta solução e bem melhor um pergunra para colocar tb no lado esquerdo tipo tb nos dois lados como faria grato

Unknown disse...

Glauber, Parabens pelos posts, vc eh fera...
seguinte, minha tela principal, eh um mapa, quando deslizo o mapa para a direita, ta abrindo o MENU. tem como deixar essa opção habilitada SOMENTE para quando o usuario clicar no botao? Obrigado!!

Nelson Glauber disse...

Oi Luis,

Nesse caso, acho melhor você colocar um botão ou algo para ativar o sliding.

4br4ç05,
nglauber

Anônimo disse...

Boa tarde.
Como seria pra colocar uma imagem pra cada menu ?

Nelson Glauber disse...

Oi Anônimo,

É normal a qualquer ListView. Você deve setar o adapter da ListView.

4br4ç05,
nglauber

Pablo Rodrigo disse...

Olá Glauber,

Tem também outra biblioteca que é a SlidingMenu. Muito boa mesmo e bem mais simples de se utilizar, pois não é necessário ficar definindo o widget nos XML. Trabalha muito nem em conjunto com a Sherlock, e é bem pequena, apenas quatro classes. Gosto mais dela devido ao efeito final, que ao abrir o menu lateral também "empurra" a actionbar, isso a lib da Google não faz. Fica a dica.

Pablo Rodrigo disse...

Olá Glauber, existe também a biblioteca SlidingMenu. Na minha opinião é bem melhor que a da Google, pois além de não ser necessário definir o widget no XML, ela também "empurra" a actionbar. Fica a dica. Valeu.

Le disse...

Como eu faço para ativar o slidingmenulayout apenas por um botão específico e bloqueálo se o usuário arrastar na tela, pois estou usando uma map.

Nelson Glauber disse...

Oi Pablo,

Na minha opinião, a melhor opção é o DrawerLayout...
http://developer.android.com/training/implementing-navigation/nav-drawer.html

Oi Le,

Acho que isso resolve seu problema.
http://stackoverflow.com/questions/17025957/disable-gesture-listener-on-drawerlayout

4br4ç05,
nglauber

Unknown disse...

Eu fiz tudo certinho aqui e tenho 5 opções no menu criado, e cada opção leva pra uma tela diferente. Como eu faria para associar cada item a tela correspondente?
Vlw, ótimo tutorial.

Nelson Glauber disse...

Oi Juninho,

O ideal é carregar um fragment dinamicamente para cada opção do seu menu.
http://developer.android.com/guide/components/fragments.html#Adding
Se o fragmento aberto, precisar exibir mais detalhes, aí você abre outra Activity.

4br4ç05,
nglauber

Unknown disse...

Como faço para cancelar o slider quando escorregar o dedo na tela, ou deixar somente ativo com o click do botão?

Nelson Glauber disse...

Oi Ricardo,

A recomendação é que seja usado o DrawerLayout e nele você pode usar o código abaixo:

mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);

Veja como implementar o DrawerLayout aqui:
http://developer.android.com/training/implementing-navigation/nav-drawer.html

4br4ç05,
nglauber