Вкладки - это обычный и простой шаблон, он превосходит дизайн мобильных или веб-приложений. Однако, чтобы избежать чрезмерного использования, нам нужны некоторые пояснения по поводу его использования.

Для чего это нужно?
Ключевой особенностью является информирование пользователя об альтернативных представлениях и возможность частого переключения между ними. Это может помочь организовать наборы данных (музыка ›рок, поп, джаз, свинг) или просмотры с похожими аспектами (музыка› все, недавние, рекомендуемые).

Для чего это НЕ?
Приложения с глубокой структурой навигации или приложения с одной базовой функцией. Классифицировать приложение подобным образом может быть непросто, но в большинстве случаев оно не подходит.

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

Каждая вкладка должна быть одинаково важна

Иногда это может немного сбивать с толку, но постарайтесь довести дело до основ. Вот небольшой пример:
Хорошее использование: ужасы, комедии, триллеры, драма.
Неправильное использование: блог, магазин, «Свяжитесь с нами», «О нас».

Соблюдайте принципы верстки

Даже если мы собираемся настроить вид, есть некоторые моменты, которые мы никогда не нарушим. Я знаю, что с некоторыми вы можете не согласиться, но, на мой взгляд, именно они делают макет ясным и понятным для пользователя. Прежде всего, представьте вкладки как одну строку, горизонтальную или вертикальную. Во-вторых, всегда выделяйте выбранную вкладку, чтобы пользователи знали, где они находятся в приложении. В-третьих, не вкладывайте вкладки. В-четвертых, настройте название вкладки или представление в соответствии с ее содержанием, чтобы ведущая вкладка раздела фотографий с именем «фотографии», а не, например, «Запись».

Сохраняйте единый дизайн вкладок

Это может быть проблематично в двух случаях. Первый - это размер вкладки, который должен быть похож на каждую вкладку. Вам следует избегать длинных описаний на вкладке, потому что легче настроить размер вкладок, состоящих из одного слова, друг для друга. Второй - если вы решите использовать что-то большее, чем текстовые вкладки, например, с изображениями, вы должны сохранить шаблон для каждой из них и не комбинировать вкладки изображений с текстовыми вкладками. Это приносит ненужный беспорядок.

Вышеупомянутая концепция должна быть довольно общей и в основном применима не только к приложениям для Android. Теперь давайте напишем код! И это будет очень специфично для Android!

Использование по умолчанию

Во-первых, для использования android.support.design.widget.TabLayout добавьте зависимость поддержки дизайна к build.gradle:

compile 'com.android.support:design:__NEWEST_VERSION_HERE__'

Затем мы можем определить TabLayout в XML макета. Самое простое использование TabLayout - использовать его как любой другой контейнер представления:

<android.support.design.widget.TabLayout
        android:id="@+id/myTabLayout"
        android:layout_height="wrap_content"
        android:layout_width="match_parent">

        <android.support.design.widget.TabItem
            android:text="DOGS"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />

        <android.support.design.widget.TabItem
            android:text="CATS"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />

    </android.support.design.widget.TabLayout>

Помните, что в TabLayout можно добавлять только экземпляры TabItem.

Затем, чтобы предпринять некоторые действия, когда вкладка выбрана, мы должны добавить слушателя в макет вкладки:

myTabLayout.addOnTabSelectedListener( object : TabLayout.OnTabSelectedListener{
            override fun onTabReselected(tab: TabLayout.Tab?) {}

            override fun onTabUnselected(tab: TabLayout.Tab?){}

            override fun onTabSelected(tab: TabLayout.Tab?) {
                Toast.makeText(applicationContext,tab!!.text,Toast.LENGTH_SHORT).show()
            }

        })

в методе onTabSelected у нас есть доступ к выбранной вкладке и всем ее элементам просмотра, например, текст. Особенно полезно поле position, которое можно использовать как индекс для другого контейнера или как ссылку на другое представление.

Программное добавление вкладок

Чтобы добавить вкладки программно, а не определять их в xml, просто используйте метод addTab(). Чтобы добавить вкладку с одним текстом, просто определите ее так:

val tab = myTabLayout.newTab()
tab.text = "DOGS"
myTabLayout.addTab(tab)

Пользовательский просмотр вкладки

Самое интересное - наконец-то добавить более интересный вид наших вкладок вместо использования только текста. Давайте объединим текст с красивыми изображениями! Нравится:

Определите круглый фон для нашего изображения под res/drawable:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#ffffff" />
    <size
        android:width="48dp"
        android:height="48dp" />
</shape>

Затем custom_tab.xml под res/layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="#888"
    android:layout_marginTop="10dp"
    android:layout_marginStart="10dp"
    android:gravity="center">

    <ImageView
        android:padding="15dp"
        android:id="@+id/tabImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="centerInside"
        android:background="@drawable/white_circle" />

    <TextView
        android:paddingTop="5dp"
        android:id="@+id/tabText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000"
        android:textSize="12sp" />

</LinearLayout>

Под drawable снова добавьте несколько изображений, которые вы хотите использовать для своих вкладок. Допустим, это dog.png и cat.png. Теперь добавить аккуратную вкладку с вашим индивидуальным макетом будет так же просто, как и в прошлый раз.

val tab = LayoutInflater.from(applicationContext).inflate(R.layout.custom_tab, null)
tab.tabImage.setImageResource(R.drawable.dog)
tab.tabText.text = "DOG"
myTabLayout.addTab(myTabLayout.newTab().setCustomView(tab))

Небольшая модная настройка

В конце я хотел бы поделиться с вами своей концепцией представления TabLayout, не стесняйтесь черпать вдохновение. Вот как это будет выглядеть:

Для этого нам нужно переопределить некоторые функции TabLayout! Вот с чего мы начнем:

class SlideLayout @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0) : TabLayout(context, attrs, defStyleAttr) {

      init {
        addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {

            override fun onTabSelected(tab: TabLayout.Tab) {
                tab.customView?.tabText?.setTypeface(null, Typeface.BOLD)
            }

            override fun onTabUnselected(tab: TabLayout.Tab) {
                tab.customView?.tabText?.setTypeface(null, Typeface.NORMAL)
            }

            override fun onTabReselected(tab: TabLayout.Tab) = Unit

        })
    }

}

Самое простое изменение - сделать текст вкладки полужирным, когда он выделен. Нам нужно addOnTabSelectedListener, где мы можем реализовать поведение вкладки при выборе. Затем просто примените typeface, как указано выше! В определении XML удалите выделение со следующим атрибутом:

<com.bob.tabs.SlideLayout
    ...
    app:tabIndicatorHeight="0dp"

Первый шаг сделан.

Вкладки по центру

Следующее, что мы сделаем, - это отобразить вкладки с выбранной вкладкой в ​​центре. Добавьте этот атрибут в определение XML:

<com.bob.tabs.SlideLayout
    ...
    app:tabMode="scrollable"

Это позволит вкладкам выходить за пределы экрана. Как следствие, выбор разных вкладок будет прокручивать весь макет и отображать выбранный в центре, если остальные вкладки заполняют оставшуюся часть экрана. Это означает, что несколько первых и нескольких последних вкладок не будут централизованы, а будут выровнены по краю родительского представления. Но! Мы можем это изменить! В onLayout методе давайте подумаем. Мы добавим отступ к первой и последней вкладке, чтобы она всегда была в центре, и вот как мы можем ее вычислить:

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    super.onLayout(changed, l, t, r, b)

    val lastTabIndex = getTabContainer()?.childCount?.minus(1) ?: 0
    val firstTab = getTabView(0)
    val lastTab = getTabView(lastTabIndex)

    if (firstTab != null && lastTab != null) {
        val paddingLeft = width / 2 - firstTab.width / 2
        val paddingRight: Int = width / 2 - lastTab.width / 2

        ViewCompat.setPaddingRelative(getTabContainer(), paddingLeft, 0, paddingRight, 0)
    }
}

А вот несколько вспомогательных методов:

private fun getTabContainer(): ViewGroup? = getChildAt(0) as? ViewGroup
private fun getTabView(position: Int): View? = getTabContainer()?.getChildAt(position)

Изменение размера

Теперь последняя особенность! Давайте изменим размер вкладок, когда они будут удалены от центра, чтобы мы могли создать впечатление, что они находятся на карусели. Определите несколько мультипликаторов масштабирования и массив справки:

private val MAX_ZOOM = 1.0f
private val MIN_ZOOM = 0.6f
private var screenPos = IntArray(2)

Тогда сделайте немного математики еще раз! На этот раз нам нужно переопределить метод onDraw:

override fun onDraw(canvas: Canvas) {

        getTabContainer()?.let {
            for (i in 0 until it.childCount) {
                getTabView(i)?.let {

                    it.getLocationOnScreen(screenPos)
                    val pos = screenPos[0]
                    val width = it.width

                    val scale: Float
                    val tabCenter = pos + width / 2

                    if (tabCenter <= 0 || getWidth() <= tabCenter) {
                        scale = MIN_ZOOM
                    } else {
                        val sliderCenter = (getWidth() / 2).toFloat()
                        val distance = Math.abs(sliderCenter - tabCenter)
                        scale = MAX_ZOOM - (MAX_ZOOM - MIN_ZOOM) * distance / sliderCenter
                    }

                    /* View draw start in left top corner */
                    it.pivotY = 0f

                    it.scaleX = scale
                    it.scaleY = scale

                }
            }
        }

        super.onDraw(canvas)
    }

При расчете масштабирования вкладки нам необходимо определить, насколько далеко (distance) находится наша вкладка от центра (sliderCenter). Основываясь на том, что мы выберем, масштабируйте множитель между MIN_ZOOM и MAX_ZOOM.

Вот и все, ваши скользящие вкладки готовы к использованию!

Полный код вы можете найти на моем github аккаунте. Он содержит еще больше функций с вкладками прокрутки и закрепления в центре. Не стесняйтесь тестировать, использовать и изменять!

Использованная литература:

  1. Вкладки / material.io
  2. TabLayout / developer.android.com

Радек Печёткевич, младший инженер-программист @ Bright Inventions Электронная почта

Первоначально опубликовано на brightinventions.pl