Реализация BottomAppBar. Часть 3: Поведения для Android

https://medium.com/material-design-in-action/implementing-bottomappbar-behavior-fbfbc3a30568
  • Перевод
  • Tutorial

Реализация BottomAppBar. Часть 3: Поведения для Android


BottomAppBar — это один из новых Android Material компонентов, которые были представлены на Google I/O 2018. Благодаря перемещению Navigation Drawer и меню приложения в нижнюю часть экрана, BottomAppBar радикально меняет внешний вид Android приложений.


В первой и второй частях нашей серии статей про BottomAppBar мы познакомились с BottomAppBar и обсудили его атрибуты. Также мы объяснили, как реализовать Navigation Drawer и меню приложения в рамках BottomAppBar.


Поведение


Согласно Material Design, компоненты приложения не являются статичными. Они могут перемещаться или трансформироваться, т.е. иметь какое-то поведение. Material Design также формирует некие рамки для такого поведения. В этой статье мы обсудим детали реализации рекомендуемого поведения для BottomAppBar, которое представлено на странице гайдлайнов для BottomAppBar.


Макет


Первый гайдлайн описывает макет BottomAppBar. Вот что предлагается:


Для разных по смыслу экранов приложения, можно изменять макет и набор пунктов меню в BottomAppBar. Например, можно отображать больше или меньше пунктов меню в зависимости от того, что лучше всего подходит для конкретного экрана.

Гайдлайн макета


Основываясь на этом гайдлайне, на главных экранах рекомендуется использовать макет BottomAppBar, показывающий несколько пунктов в меню и центрированную FAB (Floating Action Button). На второстепенных экранах, переход на которые осуществляется с главных, макет BottomAppBar должен состоять из выровненной по правому краю FAB и нескольких дополнительных пунктов в меню. Переходы между этими двумя экранами должны выполняться надлежащим образом. Gif сверху демонстрирует этот гайдлайн.


Теперь давайте посмотрим, как можно реализовать это поведение. У нас есть два xml-файла в папке res/menu для меню каждого экрана:


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/app_bar_search"
        android:icon="@drawable/baseline_search_white_24"
        android:title="@string/action_search"
        app:showAsAction="ifRoom"/>

</menu>

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/app_bar_mail"
        android:icon="@drawable/baseline_mail_white_24"
        android:title="@string/action_mail"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@+id/app_bar_delete"
        android:icon="@drawable/baseline_delete_white_24"
        android:title="@string/action_delete"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@+id/app_bar_archieve"
        android:icon="@drawable/baseline_archive_white_24"
        android:title="@string/action_archieve"
        app:showAsAction="ifRoom"/>

</menu>

Когда происходит переход между экранами, например, по нажатию кнопки TOGGLE SCREEN в нашем случае, макет BottomAppBar, включая меню и FAB, должен измениться. Вот базовый код для такого поведения макета BottomAppBar:


// Hide navigation drawer icon
bottom_app_bar.navigationIcon = null

// Move FAB from the center of BottomAppBar to the end of it
bottom_app_bar.fabAlignmentMode = BottomAppBar.FAB_ALIGNMENT_MODE_END

// Replace the action menu
bottom_app_bar.replaceMenu(bottomappbar_menu_secondary)

// Change FAB icon
fab?.setImageDrawable(baseline_reply_white_24)

Если вы хотите сделать анимированные переходы, нужен дополнительный код. Вы можете изучить исходный код, прикреплённый в конце этой статьи, в котором найдёте анимацию.


Скроллирование


Скроллирование — важный триггер поведения для таких компонентов, как BottomAppBar. На странице гайдлайнов Material Design рекомендуется следующее поведение для этого случая:


При скроллировании BottomAppBar может появиться или исчезнуть:
— Скроллирование вниз скрывает BottomAppBar. Если на нём была FAB, она отсоединяется от панели и остается на экране.
— Скроллирование вверх показывает BottomAppBar и снова присоединяет его к FAB, если она там была.

Ниже приведена демонстрация поведения BottomAppBar при скроллировании.


Скроллирование и BottomAppBar


Чтобы использовать это поведение, BottomAppBar и FAB должны быть прямыми дочерними элементами CoordinatorLayout. Затем мы включаем hideOnScroll и устанавливаем флаги скроллирования для BottomAppBar:


<com.google.android.material.bottomappbar.BottomAppBar
    android:id="@+id/bottom_app_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    app:fabAlignmentMode="center"
    app:hideOnScroll="true"
    app:layout_scrollFlags="scroll|enterAlways"/>

Этого вполне достаточно для реализации такого поведения BottomAppBar.


Возвышение


У каждого компонента в мире Material Design есть возвышение, аналогичное нашему физическому миру. У BottomAppBar возвышение — 8dp, а само содержимое экрана возвышается на 0dp. FAB в статичном состоянии возвышается на 12dp. Два компонента, которые мы ещё вспомним в этой статье, Navigation Drawer и Snackbar, возвышаются на 16dp и 6dp соответственно.


Как правило, Snackbar — это компонент для уведомления пользователя, выскакивающий из нижней части экрана. Но если на экране есть BottomAppBar или Navigation Drawer, поведение Snackbar должно измениться. В этих случаях Snackbar следует показывать над нижними компонентами. Вот демонстрация и соответствующий код для реализации:


Snackbar и BottomAppBar


private fun displayMaterialSnackBar() {
        val marginSide = 0
        val marginBottom = 550
        val snackbar = Snackbar.make(
                coordinatorLayout2,
                "FAB Clicked",
                Snackbar.LENGTH_LONG
        ).setAction("UNDO") {  }
        // Changing message text color
        snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.colorSnackbarButton))

        val snackbarView = snackbar.view
        val params = snackbarView.layoutParams as CoordinatorLayout.LayoutParams

        params.setMargins(
                params.leftMargin + marginSide,
                params.topMargin,
                params.rightMargin + marginSide,
                params.bottomMargin + marginBottom
        )

        snackbarView.layoutParams = params
        snackbar.show()
}

Как мы уже упоминали, Navigation Drawer возвышается на 16dp, что означает — согласно гайдлайну —


Меню, выпадающие из BottomAppBar (например, Navigation Drawer), открываются как модальные окна на уровень выше, чем сам BottomAppBar.

Ниже приведена реализация нашего Navigation Drawer:


Поведение Navigation Drawer


Navigation Drawer является модальным окном и поэтому следует приведённому выше правилу реализации.


Детали реализации этого поведения выглядят следующим образом. В папке res/menu должен быть создан xml-файл меню для Navigation View, который будет использован в Navigation Drawer:


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <group android:checkableBehavior="none">
        <item
            android:id="@+id/nav1"
            android:icon="@drawable/baseline_mail_white_24"
            android:title="@string/nav_item1" />
        <item
            android:id="@+id/nav2"
            android:icon="@drawable/baseline_bookmark_white_24"
            android:title="@string/nav_item2" />
        <item
            android:id="@+id/nav3"
            android:icon="@drawable/baseline_message_white_24"
            android:title="@string/nav_item3" />
        <item
            android:id="@+id/nav4"
            android:icon="@drawable/baseline_note_white_24"
            android:title="@string/nav_item4" />
        <item
            android:id="@+id/nav5"
            android:icon="@drawable/baseline_location_on_white_24"
            android:title="@string/nav_item5" />
        <item
            android:id="@+id/nav6"
            android:icon="@drawable/baseline_sync_white_24"
            android:title="@string/nav_item6" />
        <item
            android:id="@+id/nav7"
            android:icon="@drawable/baseline_cloud_upload_white_24"
            android:title="@string/nav_item7" />
        <item
            android:id="@+id/nav8"
            android:icon="@drawable/baseline_favorite_white_24"
            android:title="@string/nav_item8" />
        <item
            android:id="@+id/nav9"
            android:icon="@drawable/baseline_chrome_reader_mode_white_24"
            android:title="@string/nav_item9" />
        <item
            android:id="@+id/nav10"
            android:icon="@drawable/baseline_select_all_white_24"
            android:title="@string/nav_item10" />
        <item
            android:id="@+id/nav11"
            android:icon="@drawable/baseline_sort_white_24"
            android:title="@string/nav_item11" />
        <item
            android:id="@+id/nav12"
            android:icon="@drawable/baseline_access_time_white_24"
            android:title="@string/nav_item12" />
        <item
            android:id="@+id/nav13"
            android:icon="@drawable/baseline_data_usage_white_24"
            android:title="@string/nav_item13" />

    </group>
</menu>

Затем должен быть создан файл макета для фрагмента, использующего Navigation Drawer:


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/navigation_view_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:behavior_hideable="true"
    app:layout_behavior="@string/bottom_sheet_behavior">

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginTop="4dp"
        android:paddingBottom="40dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view2"
        app:menu="@menu/bottom_nav_drawer_menu"
        app:theme="@style/NavigationDrawerStyle" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginTop="16dp"
        android:fontFamily="@font/rubik_medium"
        android:text="@string/bottom_sheet_name"
        android:textColor="@color/colorAccent"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:text="@string/bottom_sheet_email"
        android:textColor="@color/colorAccent"
        app:layout_constraintStart_toStartOf="@+id/textView"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="24dp"
        android:background="@drawable/baseline_account_circle_black_48"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView" />

    <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="2dip"
        android:layout_marginTop="15dp"
        android:background="#447e7e7e"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <ImageView
        android:id="@+id/close_imageview"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/baseline_close_black_24"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="@+id/textView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

Этот файл макета содержит Navigation View и другие компоненты, формирующие макет для Navigation Drawer. Чтобы создать этот макет, нам нужен класс фрагмента, расширяющий BottomSheetDialogFragment:


class BottomNavigationDrawerFragment: BottomSheetDialogFragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_bottom_navigation_drawer, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        navigation_view.setNavigationItemSelectedListener { menuItem ->
            // Bottom Navigation Drawer menu item clicks
            when (menuItem.itemId) {
                // R.id.nav1 -> context!!.toast(getString(R.string.nav1_clicked))
            }
            // Add code here to update the UI based on the item selected
            // For example, swap UI fragments here
            true
        }
    }
}

При клике по значку Navigation Drawer создаётся экземпляр этого фрагмента, который показывается в виде модального окна:


override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        when (item!!.itemId) {
            android.R.id.home -> {
                val bottomNavDrawerFragment = BottomNavigationDrawerFragment()
                bottomNavDrawerFragment.show(supportFragmentManager, bottomNavDrawerFragment.tag)
            }
        }
        return true
}

Это статья завершает нашу серию статей про BottomAppBar. Найти исходный код этой статьи вы можете на Github. Комментируйте и задавайте вопросы.


← Реализация BottomAppBar. Часть 1: Material компоненты для Android
← Реализация BottomAppBar. Часть 2: Меню и элемент управления Navigation Drawer

Поделиться публикацией
Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое