Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Обновлено 24 октября 2023 года
Компонент из раздела Containers. Размещается в нижней части экрана. Может содержать от трёх до пяти элементов.
Подключаем стандартным способом, либо студия сама предложит установить библиотеку, если она ещё не прописана.
implementation("com.google.android.material:material:1.10.0")
По названию видно, что компонент должен размещаться в нижней части экрана. В теории ничто не мешает разместить его в верхней части, но народ вас не поймёт.
Основные требования к компоненту:
Создадим простой вариант.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.Material3.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/true_solid" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/menu_bottom" />
</LinearLayout>
Для кастомизации можете задействовать следующие атрибуты
Меню создаётся стандартным способом в папке res/menu.
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/inboxFragment"
android:icon="@android:drawable/ic_dialog_email"
android:title="Inbox" />
<item
android:id="@+id/sentItemsFragment"
android:icon="@android:drawable/ic_menu_send"
android:title="Sent Items" />
<item
android:id="@+id/aboutFragment"
android:icon="@android:drawable/ic_menu_help"
android:title="About" />
</menu>
При попытке добавить шестой элемент в меню вы получите ошибку при запуске программы Caused by: java.lang.IllegalArgumentException: Maximum number of items supported by BottomNavigationView is 5. Limit can be checked with BottomNavigationView#getMaxItemCount().
Навигация между фрагментами происходит стандартным способом. Идентификаторы в меню должны совпадать с идентификаторами фрагментов. Никакого кода писать не нужно. Процесс создания файла навигации здесь я опущу.
В старых примерах, когда навигация ещё широко не использовалась, нужно было вручную писать код. Далее старые примеры ещё на Java.
Для отслеживания нажатия на определённый значок используется соответствующий слушатель BottomNavigationView.OnNavigationItemSelectedListener.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = findViewById(R.id.bottom_navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
// Выбор происходит по идентификаторам меню (R.id.action_search и т.д)
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= item -> {
switch (item.getItemId()) {
case R.id.action_search:
setTitle("Search");
return true;
case R.id.action_settings:
setTitle("Settings");
return true;
case R.id.action_navigation:
setTitle("Navigation");
return true;
}
return false;
};

Также был старый способ переключаться между фрагментами приблизительно следующим образом.
Fragment currentFragment = null;
switch (item.getItemId()) {
case R.id.menu_alarm_add:
currentFragment = FragmentOne.newInstance();
break;
case R.id.menu_alarm_list:
currentFragment = FragmentTwo.newInstance();
break;
case R.id.menu_alarm_off:
currentFragment = FragmentThree.newInstance();
break;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.framelayout, currentFragment);
transaction.commit();
return true;
Можно скрывать нижнюю панель при прокрутке. Для этого придётся создать отдельный класс и использовать макет CoordinatorLayout.
public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {}
Старое: Напишем аналогичный пример на Kotlin и добавим обработку событий щелчка на уже выбранном элементе через setOnNavigationItemReselectedListener.
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.bottomnavigation
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
val bottom_navigation_view: BottomNavigationView = findViewById(R.id.bottom_navigation_view)
bottom_navigation_view.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.action_one -> {
textView.text = "Выбран элемент One"
true
}
R.id.action_two -> {
textView.text = "Выбран элемент Two"
true
}
R.id.action_three -> {
textView.text = "Выбран элемент Three"
true
}else -> false
}
}
bottom_navigation_view.setOnNavigationItemReselectedListener {
when (it.itemId) {
R.id.action_one -> textView.text = "Повторно выбран элемент One"
R.id.action_two -> textView.text = "Повторно выбран элемент Two"
R.id.action_three -> textView.text = "Повторно выбран элемент Three"
}
}
}
}
Элементы можно выбрать программно. Добавим кнопку в макет и напишем код.
button.setOnClickListener {
bottom_navigation_view.menu.getItem(1).isChecked = true
textView.text = "Программно выбран второй элемент"
}
Также можно программно установить цвет для BottomNavigationView.
button.setOnClickListener {
bottom_navigation_view.setBackgroundColor(Color.GREEN)
textView.text = "Программно установлен цвет"
}
Пример у меня не заработал, если явно установлен цвет в атрибутах app:itemBackground="@android:color/darker_gray", поэтому его нужно убрать.
Если у вас нет особой надобности использовать свой цвет для нижней панели, то используйте рекомендованный вариант в стиле Material Design:
app:itemBackground="@color/colorPrimary"
В этом случае вы можете использовать свои цвета для значков и текста вместо белого/чёрного. Но программно потом изменить цвет не сможете (см. предыдущий пример).
Также можно сделать элемент меню недоступным. Добавим в селектор новую настройку до предыдущих настроек в файле bottom_navigation_item_background_colors.
<item android:color="@android:color/darker_gray" android:state_enabled="false" />
Сделаем недоступным первый элемент меню
<item
android:id="@+id/action_one"
android:icon="@android:drawable/ic_dialog_map"
android:enabled="false"
android:title="One" />
Теперь первый элемент не будет реагировать на нажатия.

Видимость текста к значкам регулируется атрибутом app:labelVisibilityMode, в скобках приводится программный вариант. Если установить значение unlabeled (LABEL_VISIBILITY_UNLABELED), то текст выводиться не будет. Соответственно, значение labeled (LABEL_VISIBILITY_LABELED) будет выводить текст у всех значков. Значение selected (LABEL_VISIBILITY_SELECTED) будет выводить текст только у выбранного элемента меню. По умолчанию используется значение auto (LABEL_VISIBILITY_AUTO), когда при наличии трёх и меньше значков текст выводится у всех элементов, а если значков четыре-пять, то выводится текст только у выбранного элемента.
// XML
<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:labelVisibilityMode="selected" />
// Code
bottom_navigation_view.labelVisibilityMode = LabelVisibilityMode.LABEL_VISIBILITY_SELECTED
Атрибут app:itemHorizontalTranslationEnabled приподнимает элемент меню, когда он выбран. Работает в связке с предыдущим атрибутом, который должен иметь либо значение selected либо auto с 4-5 элементами.
// XML
<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:labelVisibilityMode="selected"
app:itemHorizontalTranslationEnabled="true" />
// Code
bottom_navigation_view.isItemHorizontalTranslationEnabled = true
У элементов меню можно выводить бейджики. В простом случае это простой кружочек, который может сигнализировать о поступлении нового письма, рождении котёнка и т.д. Выводим бейдж у первого элемента меню.
val badge = bottom_navigation_view.getOrCreateBadge(R.id.action_one);
badge.isVisible = true
Переменная badge является экземпляром класса BadgeDrawable. Есть несколько методов для работы с классом. Не забывайте удалять бейдж, когда он больше не требуется.
bottom_navigation_view.getOrCreateBadge(R.id.item1) // Show badge
bottom_navigation_view.removeBadge(R.id.item1) // Remove badge
val badge = bottom_navigation_view.getBadge(R.id.item1) // Get badge
У класса BadgeDrawable есть несколько полезных методов: setNumber/getNumber/hasNumber/clearBadgeNumber. Например, мы можем вывести число.
badge.number = 9
Методы setMaxCharacterCount/getMaxCharacterCount устанавливают максимальное число символов, после которого большие значения выводятся с плюсом. По умолчанию используется четыре символа.
badge.maxCharacterCount = 2
badge.number = 102
Управлять местоположением выводимых чисел в бейдже можно через методы setBadgeGravity/getBadgeGravity, используя константы TOP_END (по умолчанию), TOP_START, BOTTOM_END, BOTTOM_START.
Также можно задать отступы через методы setHorizontalOffset/getHorizontalOffset/setVerticalOffset/setVerticalOffset.
Можно настроить цвет бейджей через атрибуты или методы класса BadgeDrawable.
badge.badgeTextColor = Color.MAGENTA
badge.backgroundColor = Color.CYAN
При долгом нажатии на элементе меню выводится подсказка (либо прохождении мышки на соответствующих устройствах). По умолчанию выводится текст, определённый в атрибуте android:title. Можно переопределить текст через атрибут app:tooltipText.
<item
android:id="@+id/action_three"
android:icon="@android:drawable/ic_dialog_email"
android:title="Three"
app:tooltipText="Письмо коту"/>
После какого-то обновления подсказка у меня перестала работать. Не разбирался.>
Можно получить интересный результат, если поместить BottomNavigationView внутри BottomAppBar с использованием FloatingActionButton. Но тут не обошлось без хитростей.
Сделаем разметку экрана.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
tools:text="Text" />
<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:fabCradleMargin="20dp"
app:fabCradleRoundedCornerRadius="20dp"
app:fabCradleVerticalOffset="10dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:itemIconTint="@color/teal_200"
app:itemTextColor="@color/purple_200"
app:contentInsetStart="0dp"
app:contentInsetLeft="0dp"
android:background="@android:color/transparent"
app:menu="@menu/menu_bottom_navigation" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_action_cat"
app:layout_anchor="@id/bottom_app_bar"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Здесь важно обратить внимание на три атрибута. Атрибут android:background устанавливает прозрачность, иначе подложка компонента некрасиво будет видна на экране. Два другие атрибута (выделены в коде жирным) убирают смещение. Оно хорошо видно, если убрать на время прозрачность. Как вариант, можно было использовать вариант android:layout_marginEnd="16dp", но нет гарантии, что значение 16dp будет неизменным в других обновлениях.
На этом хитрости не заканчиваются. Если элементов меню BottomNavigationView будет нечётным, то один из значков может оказаться под кнопкой FloatingActionButton и будет опять некрасиво. Чтобы избежать этой проблемы, мы добавим фейковый элемент с пустым заголовком.
<?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">
<item
android:id="@+id/action_one"
android:icon="@android:drawable/ic_dialog_map"
android:title="One" />
<item
android:id="@+id/action_two"
android:icon="@android:drawable/ic_dialog_info"
android:title="Two" />
<item
android:id="@+id/placeholder"
android:title="" />
<item
android:id="@+id/action_three"
android:icon="@android:drawable/ic_dialog_email"
android:title="Three"
app:tooltipText="Письмо коту" />
<item
android:id="@+id/action_four"
android:icon="@android:drawable/ic_btn_speak_now"
android:title="Four" />
</menu>
Хотя мы убрали прозрачность в XML, этого недостаточно. А также надо убрать возможность щёлкать по фейковому элементу. Поэтому добавляем код.
val bottomNavigationView: BottomNavigationView = findViewById(R.id.bottom_navigation_view)
bottomNavigationView.background = null
bottomNavigationView.menu.getItem(2).isEnabled = false
bottomNavigationView.setOnNavigationItemSelectedListener {
...
}
bottomNavigationView.setOnNavigationItemReselectedListener {
...
}