Реалізація 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 icon FAB
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. Коментуйте і задавайте питання.