Save the date! Android Dev Summit is coming to Mountain View, CA on November 7-8, 2018.

Criação de uma gaveta de navegação

A gaveta de navegação é um painel que exibe as principais opções do painel de navegação do aplicativo na borda esquerda da tela. Ela fica oculta a maior parte do tempo, mas é revelada quando o usuário desliza um dedo a partir da borda esquerda da tela, ou, no nível superior do aplicativo, o usuário toca no ícone do aplicativo na barra de ações.

Esta lição descreve como implementar uma gaveta de navegação usando as APIs DrawerLayout disponíveis na biblioteca de suporte.

Design da gaveta de navegação

Antes de decidir usar uma gaveta de navegação em seu aplicativo, você deve compreender os casos de uso e princípios de design definidos no guia de design de Gaveta de Navegação.

Criar um layout de gaveta

Para adicionar uma gaveta de navegação, declare sua interface de usuário com um objeto DrawerLayout como a exibição raiz do seu layout. Dentro do DrawerLayout, adicione uma visualização que contenha o conteúdo principal para a tela (seu layout primário quando a gaveta está oculta) e outra visualização que contenha o conteúdo da gaveta de navegação.

Por exemplo, o layout a seguir usa um DrawerLayout com duas visualizações filhas: um FrameLayout um para conter o conteúdo principal (povoado por um Fragment no tempo de execução) e uma ListView para a gaveta de navegação.

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

Este layout demonstra algumas características importantes de layout:

  • A visualização principal do conteúdo (o FrameLayout acima) deve ser a primeira filha no DrawerLayout porque a ordem do XML implica ordenação em “z” e a gaveta deve estar acima do conteúdo.
  • A visualização principal do conteúdo está definida para coincidir com a largura e altura da exibição pai, porque representa toda a IU quando a gaveta de navegação está oculta.
  • A visualização da gaveta (a ListView) deve especificar sua gravidade horizontal com o atributo android:layout_gravity. Para aceitar idiomas da direita para a esquerda (RTL), especifique o valor com "start" em vez de "left" (de modo a gaveta apareça no lado direito quando o layout for RTL).

  • A visualização da gaveta especifica sua largura em dp unidades e a altura corresponde à exibição pai. A largura da gaveta não deve exceder os 320dp, para que o usuário possa sempre ver uma parte do conteúdo principal.

Inicializar a lista de gaveta

Na sua atividade, uma das primeiras coisas a fazer é inicializar a lista de itens da gaveta de navegação. O modo de fazê-lo depende do conteúdo do seu aplicativo, mas as gavetas de navegação geralmente consistem em uma ListView, portanto a lista deve ser preenchida por um Adapter (como ArrayAdapter ou SimpleCursorAdapter).

Por exemplo, eis como você pode inicializar a lista de navegação com uma matriz de strings:

public class MainActivity extends Activity {
    private String[] mPlanetTitles;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPlanetTitles = getResources().getStringArray(R.array.planets_array);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set the adapter for the list view
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mPlanetTitles));
        // Set the list's click listener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        ...
    }
}

Este código também chama setOnItemClickListener() para receber eventos de clique na lista da gaveta de navegação. A próxima seção mostra como implementar essa interface e alterar a exibição de conteúdo quando o usuário seleciona um item.

Lidar com eventos de clique de navegação

Quando o usuário seleciona um item na lista de gaveta, o sistema chama onItemClick() no OnItemClickListener dado ao setOnItemClickListener().

O que você faz no método onItemClick() depende de como implementou a sua estrutura de aplicativo. No exemplo a seguir, selecionar cada item na lista insere um Fragment diferente na visualização de conteúdo principal (o elemento FrameLayout identificado pela R.id.content_frame ID):

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        selectItem(position);
    }
}

/** Swaps fragments in the main content view */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Highlight the selected item, update the title, and close the drawer
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

Escutar eventos abertos e fechados

Para escutar eventos abertos e fechados de gaveta, chame setDrawerListener() no seu DrawerLayout e passe-lhe uma implementação de DrawerLayout.DrawerListener. Essa interface fornece retornos de chamada para eventos de gaveta, como onDrawerOpened() e onDrawerClosed().

No entanto, em vez de implementar o DrawerLayout.DrawerListener, se a sua atividade contiver a barra de ações, você pode optar por estender a classe ActionBarDrawerToggle. O ActionBarDrawerToggle implementa DrawerLayout.DrawerListener para que você ainda possa modificar esses retornos de chamada, mas também facilita o bom comportamento de interação entre o ícone da barra de ações e a gaveta de navegação (discutida mais detalhadamente na próxima seção).

Como discutido no guia de design da Gaveta de Navegação, você deve modificar o conteúdo da barra de ações quando a gaveta estiver visível, como alterar o título e remover itens de ação que sejam contextuais para o conteúdo principal. O código a seguir mostra como você pode fazê-lo ao modificar métodos de retorno de chamada DrawerLayout.DrawerListener por uma instância da classe ActionBarDrawerToggle:

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

A próxima seção descreve os argumentos do construtor da ActionBarDrawerToggle e os outros passos necessários para configurá-la para lidar com interação com o ícone da barra de ações.

Abrir e fechar com o ícone do aplicativo

Os usuários podem abrir e fechar a gaveta de navegação com um gesto de deslizar da borda esquerda da tela ou em direção a ela, mas se estiver usando a barra de ações, você também deve permitir que os usuários a abram e fechem tocando no ícone do aplicativo. E o ícone do aplicativo também deve indicar a presença da gaveta de navegação com um ícone especial. Você pode implementar todo esse comportamento usando a ActionBarDrawerToggle mostrada na seção anterior.

Para fazer a ActionBarDrawerToggle funcionar, cria uma instância de com seu construtor, o que requer os seguintes argumentos:

  • Activity que hospeda a gaveta.
  • DrawerLayout.
  • Recurso drawable para usar como indicador de gaveta.

    O ícone padrão da gaveta de navegação está disponível no Pacote de download do ícone da barra de ações.

  • Um recurso de String para descrever a ação "abrir gaveta" (para acessibilidade).
  • Um recurso de String para descrever a ação "fechar gaveta" (para acessibilidade).

Portanto, independentemente de ter criado uma subclasse de ActionBarDrawerToggle como seu ouvinte de gaveta, você precisa chamar ActionBarDrawerToggle em alguns lugares por todo o ciclo de vida da sua atividade:

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...

    public void onCreate(Bundle savedInstanceState) {
        ...

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }

    ...
}

Para um exemplo completo de uma gaveta de navegação, baixe a amostra disponível no topo da página.