Статьи Navigation Drawer
Post
Cancel

Navigation Drawer

Navigation drawer - это главное меню приложения, которое выдвигается слева направо при нажатии пользователем на значок “гамбургера”. Либо свайпом слева направо. Его еще называют “шторкой” и в открытом виде выглядит так:

navigation drawer open

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

Но вот на Google I/O 2018 была предложена совершенно новая концепция навигации по приложению, для которой уже весной 2019 года выпустили стабильную версию. Первоначально носила название Navigation Architecture Component, теперь же именуется Jetpack Navigation.

Главная цель - создание приложений по типу singleActivity.

С выходом Jetpack Navigation добавление шторки в приложении значительно упростилось, уменьшилось количество кода и настроек. Ну и, конечно же, был обновлен шаблон Navigation Drawer Activity.

В примере ниже рассмотрим добавление шторки вручную.


Алгоритм добавления шторки в интерфейс

Зависимости

1
2
3
4
5
6
7
// Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

// Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

В этом же файле build.gradle должно быть:

1
2
3
4
5
6
7
android {
  ...

  kotlinOptions {
        jvmTarget = "1.8"
  }
}

Иначе словите ошибку: jvm-error

Фрагменты

Так как основная цель Jetpack Navigation - создание приложений по типу singleActivity, основной контент будет отображаться во фрагментах.

На этом этапе нужно создать классы фрагментов и макеты к ним. В моем приложении будет три фрагмента - MainFragment, SettingsFragment, AboutFragment. По внутреннему содержанию они идентичны (все отображают TextView), а значит будет достаточно создать один файл разметки.

Макет для MainFragment, SettingsFragment, AboutFragment - fragment_page.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|center_vertical"
        android:textSize="30sp" />
</FrameLayout>

Код для класса MainFragment
Для остальных фрагментов код идентичен, разница только в отображаемом тексте.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_page.*

class MainFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        inflater.inflate(R.layout.fragment_page, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // каждому фрагменту установите уникальный текст для наглядности
        tv_info.text = "Main Content"
    }
}

Навигация

Теперь для созданных фрагментов нужно выстроить навигацию.

Добавляем новую директорию в ресурсы через контекстное меню New -> Android Resource Directory. В новом окне полю Resource type задать значение navigation: navigation directory

В созданной папке добавляем новый файл nav_graph.xml.

Проще всего спроектировать навигацию через визуальный конструктор. В левом верхнем углу находится кнопка “New Destination”, с ее помощью можно добавить в граф все вышесозданные фрагменты.

При выделении фрагмента в визуальном редакторе появляется кружок в центре правого края. С помощью него выстраивается цепочка переходов от фрагмента к фрагменту.

create new action

В результате в nav_graph.xml должен получится такой код:

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
<?xml version="1.0" encoding="utf-8"?>
<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/main_fragment">

    <fragment
        android:id="@+id/main_fragment"
        android:name="com.foxy.navigationdrawer.MainFragment"
        android:label="MainContent" >
        <action
            android:id="@+id/action_main_fragment_to_about_fragment2"
            app:destination="@id/about_fragment" />
        <action
            android:id="@+id/action_main_fragment_to_settings_fragment"
            app:destination="@id/settings_fragment" />
    </fragment>
    <fragment
        android:id="@+id/about_fragment"
        android:name="com.foxy.navigationdrawer.AboutFragment"
        android:label="About" />
    <fragment
        android:id="@+id/settings_fragment"
        android:name="com.foxy.navigationdrawer.SettingsFragment"
        android:label="Settings" />
</navigation>

Обратите внимание на атрибут android:label - он отвечает за заголовок, который будет отображаться в тулбаре для каждого фрагмента.

Меню для шторки

По аналогии с навигацией, нужно добавить папку в ресурсы для меню и файл nav_drawer_menu.xml:

1
2
3
4
5
6
7
8
9
<menu
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/settings_fragment"
        android:title="Settings" />
    <item
        android:id="@+id/about_fragment"
        android:title="About" />
</menu>

Обратите внимание на идентификаторы в меню - для корректной работы они должны быть идентичны идентификаторам из nav_graph.xml.

Header для шторки

Создается в папке layouts, дизайн на ваше усмотрение. Вот, что получилось у меня (nav_header.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="192dp"
    android:padding="16dp"
    android:orientation="vertical"
    android:gravity="bottom"
    android:background="@color/colorPrimary">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:src="@drawable/ic_launcher_foreground"/>
</LinearLayout>

Объединяем все вместе в макете activity

Корневым элементом макета activity обязательно должен быть DrawerLayout, так как именно он позволяет шторке выдвигаться из края экрана. Внутри DrawerLayout’а объявляется основное содержимое экрана: toolbar, контейнер для фрагментов и сама шторка - NavigationView.

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    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">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:title="@string/app_name" />

        <fragment
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph" />
    </LinearLayout>

    <com.google.android.material.navigation.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"
        app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

Элемент <fragment> является контейнером для наших фрагментов. Подробнее об особо важных атрибутах:

  • Значение атрибута android:name="androidx.navigation.fragment.NavHostFragment" говорит о том, что данный элемент в разметке будет являться хостом для фрагментов. Указывается обязательно в таком виде без изменений, так как хост должен быть производным от NavHostFragment, который в свою очередь обрабатывает смену фрагментов местами.
  • app:defaultNavHost="true" - позволяет перехватывать нажатие на системную кнопку “Назад”, т.е. не нужно ее дополнительно отслеживать и обрабатывать.
  • app:navGraph="@navigation/nav_graph" - связывает NavHostFragment с созданным нами графом навигации.

Для NavigationView устанавливаем ранее подготовленные файлы - header и меню, а также с помощью атрибута android:layout_gravity указываем с какой стороны она будет выезжать.

Так как в макете мы объявили реализацию toolbar, требуется отключить стандартный actionBar. Для этого заходим в values/styles.xml и меняем DarkActionBar на NoActionBar. Должно получится так:

1
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

Подключение в классе 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
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var appBarConfig: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // устанавливаем свою реализацию тулбара
        setSupportActionBar(toolbar)

        val navController = findNavController(R.id.fragment_container)

        // конфигурация тулбара
        appBarConfig = AppBarConfiguration(navController.graph, drawer_layout)
        setupActionBarWithNavController(navController, appBarConfig)

        // метод, который связывает шторку с навигацией
        nav_view.setupWithNavController(navController)
    }

    // отслеживает клик по иконке гамбургера и стрелке UP
    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.fragment_container)
        return navController.navigateUp(appBarConfig) || super.onSupportNavigateUp()
    }
}

AppBarConfiguration - устанавливает в тулбаре иконку “гамбургера” и меняет ее на стрелку UP, если мы находимся не на главном экране приложения. Вместо графа в качестве первого параметра можно указать список фрагментов, у которых не будет происходить смена иконки “гамбургера” на стрелку UP:

1
appBarConfig = AppBarConfiguration(setOf(R.id.main_fragment), drawer_layout)

Запускаем приложение и наслаждаемся результатом.


Цвет иконки

Добавляем в values/styles.xml следующий код:

1
2
3
4
5
6
7
8
9
10
11
12
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="drawerArrowStyle">@style/DrawerIconStyle</item>
</style>

<style name="DrawerIconStyle" parent="Widget.AppCompat.DrawerArrowToggle">
    <item name="spinBars">true</item>
    <item name="color">@color/colorWhite</item>
</style>

Значение colorWhite задано в файле colors.xml. Соответственно можно указать любой цвет по вкусу.

Полезные ссылки

Документация - Update UI components with NavigationUI.

StartAndroid - Navigation. NavigationUI.

Полный код текущего проекта здесь.

This post is licensed under CC BY 4.0 by the author.