На прошлом уроке мы познакомились с ViewPager2 и создали андроид-приложение, в котором можно листать экраны свайпом вправо или влево. На этом уроке добавим в верхней части экрана вкладки, которые будут содержать заголовки и индикатор экрана, на котором находится пользователь в данный момент, а также рассмотрим некоторые их свойства и способы оформления.
В этом уроке будем использовать проект из прошлого урока, можно скачать его на странице урока 18 по ссылке вверху.
Добавление TabLayout в макет разметки экрана
Чтобы добавить вкладки на экран, нужно открыть макет разметки и добавить компонент com.google.android.material.tabs.TabLayout в верхней части экрана:
<?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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" app:tabMode="scrollable" app:tabIndicatorColor="@color/teal_200" app:tabIndicatorHeight="4dp" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tab_layout"/> </androidx.constraintlayout.widget.ConstraintLayout>
Для корректного размещения нужно изменить компонент ViewPager2 – высоту укажем 0dp. Таким образом, высоту компонента будет регулировать корневой ConstraintLayout по заданным ограничениям. А вместо ограничения app:layout_constraintTop_toTopOf=»parent» поставим app:layout_constraintTop_toBottomOf=»@+id/tab_layout» – чтобы верх компонента ViewPager2 был ограничен не верхней границей родительского компонента, а нижней границей компонента TabLayout.
Рассмотрим подробнее компонент com.google.android.material.tabs.TabLayout. Свойство app:tabMode=»scrollable» обеспечивает размещение в видимой части экрана только нескольких вкладок, остальные будут доступны в процессе прокрутки. Если мы не укажем это свойство, то в видимой части экрана будут одновременно отображаться все вкладки, и при большом их количестве визуальное восприятие будет затруднено.
Свойство app:tabIndicatorColor=»@color/teal_200″ указывает цвет, а app:tabIndicatorHeight=»4dp» – толщину индикатора вкладки.
Далее идут свойства ширины – указываем по родителю – и высоты – указываем по содержимому.
Последние три свойства – ограничения верхней части и боковых сторон компонента по родителю.
Реализация вкладок в MainActivity
Открываем класс MainActivity и пишем реализацию вкладок:
package info.fandroid.viewpager2app
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
class MainActivity : FragmentActivity() {
private lateinit var adapter: NumberAdapter
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = NumberAdapter(this)
viewPager = findViewById(R.id.pager)
viewPager.adapter = adapter
tabLayout = findViewById(R.id.tab_layout)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = "TAB ${(position + 1)}"
}.attach()
}
}
Инициализируем TabLayout так же, как мы это делали с ViewPager2 на прошлом уроке. Сначала объявляем переменную с ленивой инициализацией. В onCreate находим компонент TabLayout по идентификатору в макете разметки и связываем его с переменной.
Для синхронизации компонента TabLayout с ViewPager2, установки текста заголовков вкладок, а также стиля вкладок, используется класс TabLayoutMediator. Судя из его названия, это посредник, который выполняет связывание и согласование позиций списка вкладок со списком страниц ViewPager2, он слушает коллбек ViewPager2 и в соответствии с прокруткой страниц прокручивает и вкладки.
В данном случае мы обращаемся к свойству tab.text и передаем в заголовки одинаковый текст – слово «TAB» — с номером вкладки. Во второй половине урока модифицируем этот участок кода для передачи разного текста в заголовки вкладок.
Метод attach() связывает TabLayout с ViewPager2.
Запустим приложение на устройстве или эмуляторе и убедимся, что на экране добавились вкладки. Номер вкладки в заголовке соответствует номеру страницы на экране, вкладки прокручиваются синхронно со страницами на экране.

Уникальные имена для вкладок
А что, если нам нужно присвоить вкладкам уникальные имена? Давайте это реализуем.
Изменим код класса MainActivity:
package info.fandroid.viewpager2app
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
class MainActivity : FragmentActivity() {
private lateinit var adapter: NumberAdapter
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private val tabNames: Array<String> = arrayOf(
"Первый",
"Второй",
"Третий",
"Четвертый",
"Пятый",
"Шестой",
"Седьмой",
"Восьмой",
"Девятый",
"Десятый",
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = NumberAdapter(this)
viewPager = findViewById(R.id.pager)
viewPager.adapter = adapter
tabLayout = findViewById(R.id.tab_layout)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = tabNames[position]
}.attach()
}
}
Мы создаем строковый массив tabNames с набором собственных имен для каждой вкладки. Затем присваиваем имена вкладкам: tab.text = tabNames[position]. Поскольку позиция вкладки служит нам индексом для обращения к элементам массива, имена в массиве должны располагаться в том же порядке.
Мы здесь создали массив имен только из десяти элементов, и во избежание ошибки нужно также ограничить десятью и количество вкладок. Мы определили количество вкладок на прошлом уроке в классе NumberAdapter, в методе getItemCount() — проверьте и исправьте:
package info.fandroid.viewpager2app
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class NumberAdapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 10
override fun createFragment(position: Int): Fragment {
val fragment = NumberFragment()
fragment.arguments = Bundle().apply {
putInt(ARG_OBJECT, position + 1)
}
return fragment
}
}
После запуска приложения на устройстве мы видим экран с десятью прокручиваемыми вкладками и уникальными именами заголовков.

Отключение капса в заголовках вкладок
Заголовки по умолчанию отображаются капсом. Если вам нужно, чтобы они отображались как обычный текст, нужно создать собственный стиль в файле themes.xml:
<style name="MyCustomTextAppearance" parent="TextAppearance.Design.Tab"> <item name="textAllCaps">false</item> <item name="android:textAllCaps">false</item> </style>
Это вернет текст в нормальное состояние. Почему нужно указывать сразу два свойства? Это связано с особенностями работы разных версий библиотек материального дизайна.
Теперь созданный стиль нужно применить компоненту TabLayout в activity_main.xml:
<com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" app:tabMode="scrollable" app:tabIndicatorColor="@color/teal_200" app:tabIndicatorHeight="4dp" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:tabTextAppearance="@style/MyCustomTextAppearance"/>
Вуаля:

Таким образом можно создавать собственные стили и кастомизировать не только текст, но и другие свойства вкладок.
Бейджи для вкладок
Также вкладкам можно устанавливать бейджи, для этого измените код TabLayoutMediator в классе MainActivity:
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = tabNames[position]
if (position == 2) {
val badge = tab.getOrCreateBadge()
badge.number = 1
}
}.attach()
Здесь мы в заголовок третьей вкладки добавляем бейдж с числом 1. Удалить бейджи можно методом tab.removeBadge().

Иконки заголовков вкладок
Можно также установить иконки в заголовках вкладок, вместо текста или вместе с ним.
Добавим в MainActivity массив со ссылками на иконки:
private val tabNumbers: Array<Int> = arrayOf( R.drawable.baseline_looks_one_black_48, R.drawable.baseline_looks_two_black_48, R.drawable.baseline_looks_3_black_48, R.drawable.baseline_looks_4_black_48, R.drawable.baseline_looks_5_black_48, R.drawable.baseline_looks_6_black_48 )
Иконки были предварительно скачаны с сайта материального дизайна и сохранены в папку res/drawable нашего проекта. Вы можете взять их из исходников или скачать по ссылке.
Поскольку иконок всего шесть, во избежание ошибки измените на шесть количество вкладок в методе getItemCount() класса NumberAdapter.
Теперь можно установить иконки для вкладок методом tab.setIcon(tabNumbers[position]):
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = tabNames[position]
tab.setIcon(tabNumbers[position])
if (position == 2) {
val badge = tab.getOrCreateBadge()
badge.number = 1
}
}.attach()
Вот что должно получиться при запуске приложения:

Если удалить или закомментировать строку tab.text = tabNames[position], то в заголовках вкладок останутся только иконки:

Исходный код
На этом мы заканчиваем урок. Скачать исходный код можно по ссылке. Вопросы задавайте в комментариях. До встречи на следующем уроке, всем добра!