В этом уроке мы реализуем Navigation Drawer
в простом приложении на Android. Рассмотрим особенности и способы реализации этого компонента.
Краткий обзор Android Navigation Drawer
Navigation Drawer представляет собой меню, выезжающее слева или справа экрана по свайпу пользователя (плавное проведение пальцем). Это один из самых удобных и популярных компонентов пользовательского интерфейса в Android. Его удобство состоит в том, что он не занимает постоянное место на экране, может вместить большое количество информации и пользователи привыкли к его использованию.
Чтобы вызвать компонент Navigation Drawer, нужно нажать на иконку в ActionBar или свайпнуть слева или справа экрана. По нажатию на элемент Drawer Layout можно вызывать различные компоненты интерфейса подобно обычному меню.
В этой статье мы реализуем Navigation Drawer с помощью Drawer Layout API из библиотеки поддержки (Android Support Library).
Создание Android Navigation Drawer. Практика
В нашем приложении мы будем использовать три фрагмента, которые будут вызываться по нажатию на элементы компонента Navigation Drawer. За управление элементами будет отвечать адаптер и хостом будет MainActivity.
Для реализации Navigation Drawer нам понадобится класс android.support.v4.widget.DrawerLayout
в качестве корневого элемента макета activity_main.xml, в котором будут LinearLayout
c компонентом Toolbar и контейнер FrameLayout для компонентов Fragment. Список элементов меню в будет представлять ListView:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<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"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include android:id="@+id/toolbar" layout="@layout/toolbar" /> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:background="#FFF" android:choiceMode="singleChoice" android:divider="@android:color/darker_gray" android:dividerHeight="1dp" /> </android.support.v4.widget.DrawerLayout> |
С помощью тега include
мы включили компонент Toolbar
. Сам макет определим в папке res/layout/toolbar.xml:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" local:popupTheme="@style/ThemeOverlay.AppCompat.Light" local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> |
Важно! Так как мы используем Toolbar
, то в файле styles.xml нужно обязательно указать родительскую тему Theme.AppCompat.Light.NoActionBar
, в противном случае приложение «упадет» с ошибкой:
1 2 3 4 5 6 7 |
<resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources> |
Теперь определим макет для item_row.xml — единичного элемента ListView
в NavigationDrawer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/activatedBackgroundIndicator" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:padding="10dp"> <ImageView android:id="@+id/imageIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:contentDescription="@string/image_icon_desc" android:paddingRight="10dp" /> <TextView android:id="@+id/mName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toEndOf="@+id/imageIcon" android:layout_toRightOf="@+id/imageIcon" android:paddingRight="10dp" android:text="@string/item_name" android:textAppearance="?android:attr/textAppearanceListItemSmall" android:textColor="@android:color/black" /> </RelativeLayout> |
Как видите, он очень простой и содержит всего 2 виджета: TextView
для отображение названия пункта и ImageView
для отображения иконки.
Сами названия пунктов определены в файле strings.xml с помощью тега string-array
:
1 2 3 4 5 |
<string-array name="drawer_items"> <item>Связаться</item> <item>Подробнее</item> <item>Посмотреть на карте</item> </string-array> |
Теперь определим класс модели данных, которая будет использоваться адаптером для отображения в ListView:
1 2 3 4 5 6 7 8 9 10 11 |
public class ItemModel { public int icon; public String name; // модель данных используемая в адаптере DrawerItemCustomAdapter public ItemModel(int icon, String name) { this.icon = icon; this.name = name; } } |
Представим класс адаптера ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package ua.com.prologistic.navigationdrawerproject.adapter; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import ua.com.prologistic.navigationdrawerproject.R; import ua.com.prologistic.navigationdrawerproject.model.ItemModel; public class DrawerAdapter extends ArrayAdapter<ItemModel> { Context mContext; int layoutId; ItemModel data[] = null; public DrawerAdapter(Context mContext, int layoutId, ItemModel[] data) { super(mContext, layoutId, data); this.layoutId = layoutId; this.mContext = mContext; this.data = data; } @Override public View getView(int position, View convertView, ViewGroup parent) { View listItem = convertView; LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); listItem = inflater.inflate(layoutId, parent, false); ImageView imageIcon = (ImageView) listItem.findViewById(R.id.imageIcon); TextView mName = (TextView) listItem.findViewById(R.id.mName); // получаем данные из переданого массива ItemModel model = data[position]; imageIcon.setImageResource(model.icon); mName.setText(model.name); return listItem; } } |
Теперь напишем классы фрагментов, которые будут появляться на экране после нажатия на элементы меню в Navigation Drawer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package ua.com.prologistic.navigationdrawerproject.fragments; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import ua.com.prologistic.navigationdrawerproject.R; public class EmailFragment extends Fragment { public EmailFragment() {} @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_email, container, false); return view; } } |
И компоновка fragment_email.xml к нему:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/label_email" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="100dp" android:gravity="center_horizontal" android:text="@string/fragment_email_text" android:textSize="45sp" android:textStyle="bold" /> </RelativeLayout> |
Классы и макеты фрагментов практически идентичны, за исключением названий, поэтому простой создайте еще 2 таких же фрагмента:
- Класс
InfoFragment
и компоновку к нему fragment_info.xml - Класс
MapFragment
и компоновку к нему fragment_map.xml
Листинг класса MainActivity представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
package ua.com.prologistic.navigationdrawerproject; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import ua.com.prologistic.navigationdrawerproject.adapter.DrawerAdapter; import ua.com.prologistic.navigationdrawerproject.fragments.EmailFragment; import ua.com.prologistic.navigationdrawerproject.fragments.InfoFragment; import ua.com.prologistic.navigationdrawerproject.fragments.MapFragment; import ua.com.prologistic.navigationdrawerproject.model.ItemModel; public class MainActivity extends AppCompatActivity { private String[] mItemTitles; private DrawerLayout mDrawerLayout; private ListView mDrawerListView; private Toolbar mToolbar; private CharSequence mTitle; private ActionBarDrawerToggle mDrawerToggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTitle = getTitle(); mItemTitles = getResources().getStringArray(R.array.drawer_items); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerListView = (ListView) findViewById(R.id.left_drawer); setupToolbar(); ItemModel[] dItems = fillDataModel(); DrawerAdapter adapter = new DrawerAdapter(this, R.layout.item_row, dItems); mDrawerListView.setAdapter(adapter); mDrawerListView.setOnItemClickListener(new ItemClickListener()); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout.addDrawerListener(mDrawerToggle); setupDrawerToggle(); } // формируем массив с данными для адаптера private ItemModel[] fillDataModel() { return new ItemModel[]{ new ItemModel(android.R.drawable.ic_dialog_email, "Связаться"), new ItemModel(android.R.drawable.ic_dialog_info, "Подробнее"), new ItemModel(android.R.drawable.ic_dialog_map, "Посмотреть на карте") }; } // по клику на элемент списка устанавливаем нужный фрагмент в контейнер private class ItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Fragment fragment = null; // на основании выбранного элемента меню // вызываем соответственный ему фрагмент switch (position) { case 0: fragment = new EmailFragment(); break; case 1: fragment = new InfoFragment(); break; case 2: fragment = new MapFragment(); break; default: break; } if (fragment != null) { FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); mDrawerListView.setItemChecked(position, true); mDrawerListView.setSelection(position); setTitle(mItemTitles[position]); mDrawerLayout.closeDrawer(mDrawerListView); } } } @Override public boolean onOptionsItemSelected(MenuItem item) { return mDrawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); } @Override public void setTitle(CharSequence title) { mTitle = title; getSupportActionBar().setTitle(mTitle); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState(); } void setupToolbar() { mToolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(mToolbar); } void setupDrawerToggle() { mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.app_name, R.string.app_name); // Это необходимо для изменения иконки на основании текущего состояния mDrawerToggle.syncState(); } } |
Полный листинг файла strings.xml представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<resources> <string name="app_name">NavigationDrawerProject</string> <string name="item_name">Название элемента</string> <string name="image_icon_desc">Иконка элемента</string> <string name="fragment_info_text">Это макет fragment_info.xml</string> <string name="fragment_email_text">Это fragment_email.xml</string> <string name="map_fragment_text">Это map_fragment.xml</string> <string-array name="drawer_items"> <item>Связаться</item> <item>Подробнее</item> <item>Посмотреть на карте</item> </string-array> </resources> |
Файл AndroidManifest.xml остается без изменений.
Теперь нам осталось синхронизировать проект с помощью Gradle и запустить на устройстве:
Скачать проект «Пример создания Navigation Drawer в Android».
Следите за обновлениями раздела Android и подписывайтесь на обновления сайта.