В данной статье я расскажу, как быстро добавить в ваше приложение для Android боковое меню (aka Navigation Drawer) в стиле Material Design. Для этого мы воспользуемся библиотекой, любезно предоставленной Mike Penz.У вас получится Navigation Drawer, который:
- Соответствует последним рекомендациям по дизайну (Google Material Design Guidelines);
- Поддерживает использование нескольких Drawer (можно выдвигать второй справа);
- Поддерживает использование бейджей;
- Имеет простой и понятный интерфейс (API);
- Может выползать как под, так и поверх Status Bar;
- Позволяет менять иконки, цвета, бейджи во время выполнения;
- Использует AppCompat support library;
- Работает, начиная с API 14.
Помимо этого, новички обучатся интеграции сторонних библиотек в свой проект, что крайне полезно, учитывая их грандиозное разнообразие на Github.
Создание проекта
В примере будет использоваться интегрированная среда разработки Android Studio от компании Google, основанная на IntelliJ IDEA, которую сама корпорация активно продвигает. Все действия можно воспроизвести используя и другие среды, например, Eclipse. Однако статья ориентирована на новичков, а они будут в большинстве своем использовать именно Android Studio, так как именно его Google теперь и предлагает при скачивании Android SDK с developer.android.com (ранее можно было скачать Eclipse).
Итак, выбираем в меню «File» -> «New Project...»:

Заполняем имя приложения, пакета, выбираем SDK.
Создавать проект мы будем с поддержкой минимального API Level равного 14, что соответствует Android 4.0 Ice Cream Sandwich, поскольку всё, что ниже, составляет менее 8% аудитории и привносит несоизмеримо большее количество головной боли:

В последних двух окнах оставляем все по умолчанию, жмем «Finish».
Android Support Library
Для того, чтобы красивый Navigation Drawer работал на версиях Android ниже 5.0 и выглядел в стиле Material Design, необходимо включить в проект библиотеку поддержки от Google, которая носит название v7 appcompat library. В текущей версии Android Studio (1.0.2) библиотека подключается по умолчанию при создании проекта. Проверьте это в файле проекта \app\build.gradle, в разделе dependencies должна быть строка (цифры могут быть не обязательно «21.0.3»):
compile 'com.android.support:appcompat-v7:21.0.3'
а класс MainActivity должен наследоваться от ActionBarActivity
public class MainActivity extends ActionBarActivity {
Также проверьте в \res\values\styles.xml, чтобы тема приложения наследовалась от Theme.AppCompat или ее вариаций без ActionBar (мы заменим ActionBar на ToolBar), например:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
Подключение библиотеки MaterialDrawer
Добавьте в раздел dependencies файла \app\build.gradle строки
compile('com.mikepenz.materialdrawer:library:0.9.5@aar') { transitive = true }
и нажмите появившуюся в верхней части окна кнопку «Sync Now» для синхронизации вашего проекта.
Подготовка разметки для Navigation Drawer
В главный layout приложения нужно добавить ToolBar. Приведите activity_main.xml к такому виду:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimary" android:elevation="4dp" android:minHeight="?attr/actionBarSize" android:paddingTop="@dimen/tool_bar_top_padding" android:transitionName="actionBar" /> </RelativeLayout>
Создайте в папке layout файл drawer_header.xml со следующим содержанием
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:orientation="vertical" android:scaleType="fitCenter" android:src="@drawable/header"></ImageView>
этот файл — разметка для верхней части Drawer'a, в которой находится картинка. Теперь положите в папку \res\drawable\ любую картинку с именем header.jpg, которая будет отображаться в верхней части Drawer'a, например эту:

Файл \res\strings.xml, содержащий строковые ресурсы, приведите к следующему виду
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">My Application</string> <string name="action_settings">Settings</string> <string name="drawer_item_home">Home</string> <string name="drawer_item_free_play">Free Play</string> <string name="drawer_item_custom">Custom</string> <string name="drawer_item_settings">Settings</string> <string name="drawer_item_help">Help</string> <string name="drawer_item_open_source">Open Source</string> <string name="drawer_item_contact">Contact</string> </resources>
Инициализация Navigation Drawer
В методе onCreate вашей MainActivity мы инициализируем ToolBar, добавьте после setContentView следующий код:
// Handle Toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Затем инициализируем и сам Navigation Drawer, добавьте ниже:
new Drawer() .withActivity(this) .withToolbar(toolbar) .withActionBarDrawerToggle(true) .withHeader(R.layout.drawer_header) .addDrawerItems( new PrimaryDrawerItem().withName(R.string.drawer_item_home).withIcon(FontAwesome.Icon.faw_home).withBadge("99").withIdentifier(1), new PrimaryDrawerItem().withName(R.string.drawer_item_free_play).withIcon(FontAwesome.Icon.faw_gamepad), new PrimaryDrawerItem().withName(R.string.drawer_item_custom).withIcon(FontAwesome.Icon.faw_eye).withBadge("6").withIdentifier(2), new SectionDrawerItem().withName(R.string.drawer_item_settings), new SecondaryDrawerItem().withName(R.string.drawer_item_help).withIcon(FontAwesome.Icon.faw_cog), new SecondaryDrawerItem().withName(R.string.drawer_item_open_source).withIcon(FontAwesome.Icon.faw_question).setEnabled(false), new DividerDrawerItem(), new SecondaryDrawerItem().withName(R.string.drawer_item_contact).withIcon(FontAwesome.Icon.faw_github).withBadge("12+").withIdentifier(1) ) .build();
В случае появления ошибок, убедитесь, что ваша секция импортов в MainActivity выглядит так:
Секция импортов в MainActivity
import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import com.mikepenz.iconics.typeface.FontAwesome; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; import com.mikepenz.materialdrawer.model.SectionDrawerItem;
Теперь можно запустить приложение и оценить результат:

Улучшения Navigation Drawer
Чтобы Navigation Drawer еще точнее соответствовал рекомендациям от Google, можно сделать следующие улучшения (см. полный листинг MainActivity в конце статьи):
- Скрывать клавиатуру при открытии NavigationDrawer:
.withOnDrawerListener(new Drawer.OnDrawerListener() { @Override public void onDrawerOpened(View drawerView) { InputMethodManager inputMethodManager = (InputMethodManager) MainActivity.this.getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(MainActivity.this.getCurrentFocus().getWindowToken(), 0); } @Override public void onDrawerClosed(View drawerView) { } })
- Закрывать NavigationDrawer по нажатию системной кнопки «Назад»:
@Override public void onBackPressed(){ if(drawerResult.isDrawerOpen()){ drawerResult.closeDrawer(); } else{ super.onBackPressed(); } }
- Обрабатывать события клика и длинного клика на элементы Drawer'a
- Уменьшать/увеличивать значения бейджей
Реализацию всех этих улучшений вы можете посмотреть в полном листинге MainActivity:
Полный код MainActivity
package ru.sample.drawer.myapplication; import android.app.Activity; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.Toast; import com.mikepenz.iconics.typeface.FontAwesome; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; import com.mikepenz.materialdrawer.model.SectionDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.Badgeable; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.Nameable; public class MainActivity extends ActionBarActivity { private Drawer.Result drawerResult = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Инициализируем Toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); // Инициализируем Navigation Drawer drawerResult = new Drawer() .withActivity(this) .withToolbar(toolbar) .withActionBarDrawerToggle(true) .withHeader(R.layout.drawer_header) .addDrawerItems( new PrimaryDrawerItem().withName(R.string.drawer_item_home).withIcon(FontAwesome.Icon.faw_home).withBadge("99").withIdentifier(1), new PrimaryDrawerItem().withName(R.string.drawer_item_free_play).withIcon(FontAwesome.Icon.faw_gamepad), new PrimaryDrawerItem().withName(R.string.drawer_item_custom).withIcon(FontAwesome.Icon.faw_eye).withBadge("6").withIdentifier(2), new SectionDrawerItem().withName(R.string.drawer_item_settings), new SecondaryDrawerItem().withName(R.string.drawer_item_help).withIcon(FontAwesome.Icon.faw_cog), new SecondaryDrawerItem().withName(R.string.drawer_item_open_source).withIcon(FontAwesome.Icon.faw_question).setEnabled(false), new DividerDrawerItem(), new SecondaryDrawerItem().withName(R.string.drawer_item_contact).withIcon(FontAwesome.Icon.faw_github).withBadge("12+").withIdentifier(1) ) .withOnDrawerListener(new Drawer.OnDrawerListener() { @Override public void onDrawerOpened(View drawerView) { // Скрываем клавиатуру при открытии Navigation Drawer InputMethodManager inputMethodManager = (InputMethodManager) MainActivity.this.getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(MainActivity.this.getCurrentFocus().getWindowToken(), 0); } @Override public void onDrawerClosed(View drawerView) { } }) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override // Обработка клика public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { if (drawerItem instanceof Nameable) { Toast.makeText(MainActivity.this, MainActivity.this.getString(((Nameable) drawerItem).getNameRes()), Toast.LENGTH_SHORT).show(); } if (drawerItem instanceof Badgeable) { Badgeable badgeable = (Badgeable) drawerItem; if (badgeable.getBadge() != null) { // учтите, не делайте так, если ваш бейдж содержит символ "+" try { int badge = Integer.valueOf(badgeable.getBadge()); if (badge > 0) { drawerResult.updateBadge(String.valueOf(badge - 1), position); } } catch (Exception e) { Log.d("test", "Не нажимайте на бейдж, содержащий плюс! :)"); } } } } }) .withOnDrawerItemLongClickListener(new Drawer.OnDrawerItemLongClickListener() { @Override // Обработка длинного клика, например, только для SecondaryDrawerItem public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { if (drawerItem instanceof SecondaryDrawerItem) { Toast.makeText(MainActivity.this, MainActivity.this.getString(((SecondaryDrawerItem) drawerItem).getNameRes()), Toast.LENGTH_SHORT).show(); } return false; } }) .build(); } @Override public void onBackPressed() { // Закрываем Navigation Drawer по нажатию системной кнопки "Назад" если он открыт if (drawerResult.isDrawerOpen()) { drawerResult.closeDrawer(); } else { super.onBackPressed(); } } // Заглушка, работа с меню @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } // Заглушка, работа с меню @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
Альтернативы
Хотелось бы обратить ваше внимание на то, что никакая библиотека не покроет всех нужд всех программистов. Поэтому приведу список аналогичных библиотек, с подключением которых можно поиграться:
https://github.com/neokree/MaterialNavigationDrawer
https://github.com/HeinrichReimer/material-drawer
https://github.com/kanytu/android-material-drawer-template
https://github.com/balysv/material-menu
https://github.com/ikimuhendis/LDrawer
https://github.com/Zlate87/material-navigation-drawer-example
Что же касается описанной в статье библиотеки, автор легко идет на контакт и очень оперативно реагирует на замечания и feature-requets, с ним можно пообщаться, создав issue, например.
Если же вам вообще не подходит ни одна библиотека, то вы всегда можете написать свою :)
Ссылки
Готовый пример из статьи на Github: https://github.com/tral/MaterialDrawerSample;
Готовый пример с фрагментами: https://github.com/tral/MaterialDrawerFragmentSample;
Библиотека MaterialDrawer от Mike Penz: https://github.com/mikepenz/MaterialDrawer
Google Material Design Guidelines: Navigation Drawer: http://www.google.com/design/spec/patterns/navigation-drawer.html;
Dashboards: https://developer.android.com/about/dashboards/index.html?utm_source=ausdroid.net;
Support Library: https://developer.android.com/tools/support-library/index.html