Как сделать Navigation Drawer в Android

В этом уроке:

  • Как создать макет для Navigation Drawer
  • Как инициализировать Navigation Drawer
  • Как обработать событие выбора пункта в списке меню Navigation Drawer
  • Как установить слушатель (listener) на открытие и закрытие Navigation Drawer
  • Открытие и закрытие Navigation Drawer по нажатию значка приложения в тулбаре
  • Скачать образец приложения с Navigation Drawer
  • Скачать Android Design Icons
  • Как разместить Navigation Drawer под Toolbar’ом

Navigation Drawer (навигационная секция) – боковая панель, которая выводит на экран основные навигационные опции приложения в левой части экрана. Она скрыта большую часть времени, и отображается по свайпу от левого края экрана или нажатию значка приложения в ActionBar.

Этот урок описывает, как реализовать Navigation Drawer, используя API-интерфейсы, доступные в библиотеке поддержки android.support.v4.

Полный код приложения с Navigation Drawer можно скачать по ссылке внизу страницы.


Прежде чем вы решите использовать Navigation Drawer в вашем приложении, вы должны ознакомиться с примерами использования и принципами проектирования, определенными в Navigation Drawer: руководство по проектированию.

Смотрите видео, как использовать готовый шаблон Navigation Drawer Activity в Android Studio – структура шаблона, пример работы в приложении.

Создать макет Navigation Drawer


Чтобы добавить Navigation Drawer, объявите пользовательский интерфейс с DrawerLayout объектом в качестве корневого View вашего макета. Внутри , добавьте View, который содержит основной контент для экрана (основной макет, когда панель навигации скрыта) и еще один View, который содержит содержимое Navigation Drawer.

Ниже приведен код макета, который использует DrawerLayout с двумя дочерними View: FrameLayout для основного содержания ( Фрагмент , создаваемый динамически во время работы программы), и ListView для боковой панели навигации.

<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>

Этот макет имеет некоторые важные особенности:

Основной View с контентом (в FrameLayout выше) должен быть первым дочерним элементом в DrawerLayout, для того, чтобы панель навигации была над контентом.

Основному View устанавливается значение match_parent для  ширины и высоты, поскольку он представляет весь интерфейс, когда скрыт NavigationDrawer.

Для ListView необходимо указывать параметр горизонтального выравнивания с помощью атрибута android:layout_gravity. Поддержка RTL-языков (справа налево) решается указанием значения "start" вместо "left" (в таком случае Navigation Drawer будет появляться справа на RTL макете).

Ширину ListView указываем в dp, а высоту ставим match_parent. Ширина боковой панели навигации должна быть не более 320dp, чтобы пользователь мог всегда видеть часть основного контента.

Инициализируем Drawer List


В классе activity  в первую очередь необходимо инициализировать список элементов для Navigation Drawer. Как вы это сделаете зависит от контента вашего приложения. Панель навигации  включает в себя listview, поэтому список должен быть заполнен с помощью адаптера (например ArrayAdapter или SimpleCursorAdapter).

Например, вот как можно инициализировать Navigation Drawer со строкой массива:

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());

        ...
    }
}

Этот код также вызывает метод setOnItemClickListener (), чтобы обрабатывать нажатия пунктов в списке. В следующем разделе показано, как реализовать этот интерфейс и изменить вид экрана, когда пользователь выбирает элемент.

Обработка события нажатия пункта в Navigation Drawer


Когда пользователь выбирает элемент в списке, система вызывает метод onItemClick() объекта DrawerItemClickListener, реализующего интерфейс OnItemClickListener и присвоенного списку через setOnItemClickListener().

Действия в методе onItemClick() зависят от задач приложения. В следующем примере, при выборе каждого элемента списка в основное View  FrameLayout, определенный по ID R.id.content_frame) вставляется новый создаваемый фрагмент:

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

/** Смена фрагментов в основном окне программы */
private void selectItem(int position) {
    // Создание нового фрагмента и вставка изображения для показа, в зависимости от выбранной позиции
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Вставка нового фрагмента взамен существующего
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Выделение выбранного элемента списка, обновление заголовка окна и закрытие бокового меню
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

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

Слушатель для открытия и закрытия Navigation Drawer


Для прослушивания событий открытия и закрытия бокового меню , вызываем setDrawerListener() в вашем DrawerLayout и передаем его интерфейсу DrawerLayout.DrawerListener. Этот интерфейс предоставляет обратные вызовы для таких событий, как onDrawerOpened() и onDrawerClosed().

Однако, вместо реализации DrawerLayout.DrawerListener, если ваше activity включает action bar, вы можете расширить класс ActionBarDrawerToggle. Реализуя в нем интерфейс DrawerLayout.DrawerListener,  можно переопределить обратные вызовы, что упрощает взаимодействие между значком строки меню и Navigation Drawer (подробнее об этом в следующем разделе).

Как обсуждалось в гайдлайне по Navigation Drawer, когда боковое меню выезжает, нужно менять содержимое панели действий action bar, например, изменить заголовок и удалить элементы действий, которые являются контекстно-зависимыми к основному содержимому. Следующий код показывает, как вы можете сделать это путем переопределения метода обратного вызова DrawerLayout.DrawerListener класса 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) {

            /** Этот код вызывается, когда боковое меню переходит в полностью закрытое состояние. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Этот код вызывается, когда боковое меню полностью открывается. */
            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);
    }

    /* Этот код вызывается, когда мы вызываем 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);
    }
}

В следующей секции описываются аргументы конструктора ActionBarDrawerToggle и другие шаги, необходимые для установки и настройки взаимодействия со значком action bar icon.

Открыть и закрыть панель навигации по  значку приложения


Пользователи могут открывать и закрывать navigation drawer жестом от левого края экрана. Если вы используете action bar, или пришедший ему на смену в Android 5.0 API новый виджет Toolbar, вы также должны позволять пользователям открывать и закрывать navigation drawer, нажав на значок приложения. Кроме того, рядом со значком приложения должен быть специальный значок, обозначающий наличие navigation drawer. Вы можете реализовать все это поведение при помощи ActionBarDrawerToggle, как показано в предыдущем разделе.

Чтобы использовать ActionBarDrawerToggle, создайте экземпляр при помощи конструктора с такими аргументами:

  • Activity , в котором размещается боковая панель навигации.
  • DrawerLayout.
  • drawable ресурс, используемый в качестве индикатора панели.Стандартный навигационный значок панели доступен в Download the Action Bar Icon Pack.
  • Строковый ресурс для обозначения открытой панели  (для специальных возможностей).
  • Строковый ресурс для обозначения закрытой панели (для специальных возможностей).

Теперь, в зависимости от использования класса ActionBarDrawerToggle в вашем списке панели навигации, вы должны вызвать ActionBarDrawerToggle в нескольких местах жизненного цикла activity:

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);
    }

    ...
}

 

Перевод источника. Скачать исходный код.

Коментарі: 16
  1. Алексей

    Я не правильно понял проблему, на самом деле StatusBar на своем месте, это под ToolBar-ом какая-то полоса размером со StatusBar, поэтому я эту полосу принял за него принял, подскажите как от неё избавится?

    1. Виталий Непочатов
      admin (автор)

      в разных версиях по-разному отображается. Простого решения нет – легче применить сторонние библиотеки.

  2. Алексей

    т.е. используя шаблон Navigation Drawer в Android Studio нет возможности в моём случае поднять StatusBar наверх над Тoolbar-ом?

  3. Алексей

    Уважаемый admin, сделал как вы посоветовали,
    перенес toolbar из app_bar_main.xml в activity_main.xml,
    navigation drawer стал выезжал под StatusBar (почему-то StatusBar оказался под Тoolbar-ом)
    как теперь поменять местами StatusBar и Тoolbar ?

    1. Виталий Непочатов
      admin (автор)

      Используйте библиотеку https://github.com/mikepenz/MaterialDrawer – она имеет много гибких настроек. Мы ее подробно разбираем в курсе по приложению для Youtube http://www.fandroid.info/prodvinutyj-kurs-po-sozdaniyu-android-prilozheniya-dlya-youtube/

  4. Chyngyz

    Спасибо за ресурс!
    Как увеличить размер текста в activity_menu_drawer.xml ?

    1. Виталий Непочатов
      admin (автор)

      Используйте атрибут android:textSize=”18sp”

  5. Виталий Непочатов
    admin (автор)
    <?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:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <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.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>
        </LinearLayout>
  6. Алексей

    за ответ спасибо, но это не совсем мой случай, когда создаю проект на шаблоне «Navigation Drawer Activity», activity_main.xml выглядет следующим образом:

    пытался сделать по вашей рекомендации, ничего не получается…

    1. Виталий Непочатов
      admin (автор)

      Чтобы navigation drawer выезжал под toolbar’ом перенесите toolbar из app_bar_main.xml в activity_main.xml, измененный таким образом:

  7. Виталий Непочатов
    admin (автор)
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:fitsSystemWindows="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/top_parent"
        tools:context=".MainActivity">
    
    <include layout="@layout/toolbar"
        android:id="@+id/toolbar"/>
    
    <android.support.v4.widget.DrawerLayout
    android:layout_below="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/drawer_layout">
        <FrameLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/background_color"/>
    <ListView
        android:id="@+id/drawer"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:layout_below="@+id/toolbar"
        android:layout_marginTop="56dp"
        android:layout_gravity="start">
    </ListView>
    </android.support.v4.widget.DrawerLayout>
    
    </RelativeLayout>
  8. Алексей

    Использую Android Studio и шаблон “Navigation Drawer Activity”
    Запускаю пустой проект на основе этого шаблона, вижу что выдвигающаяся панель закрывает заголовок активности.
    как сделать, чтобы панель выдвигалась под заголовком?

    1. Виталий Непочатов
      admin (автор)

      Вы должны переместить DrawerLayout в качестве верхнего родителя и переместить Toolbar из DrawerLayout контейнера контента. Пример ниже:
      RelativeLayout
      ----Toolbar
      ----DrawerLayout
      ---ContentView
      ---DrawerList

  9. Олег

    Отличный пример,

    Как сделать, чтобы выпадающее меню было и на других активити без дублирования обработки нажатий на пункты меню?

    1. Виталий Непочатов
      admin (автор)

      Использовать фрагменты в приложении

  10. спасибо огромное за ваши труды !!

Додати коментар