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 и 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 следует показывать над нижними компонентами. Вот демонстрация и соответствующий код для реализации:
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 является модальным окном и поэтому следует приведённому выше правилу реализации.
Детали реализации этого поведения выглядят следующим образом. В папке 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