Как использовать TabLayout с ViewPager2 в Android

Я хочу использовать com.google.android.material.tabs.TabLayout компонент с новой ViewPager реализацией androidx.viewpager2.widget.ViewPager2 Android. Однако метод setupWithViewPager(..), предоставленный TabLayout, поддерживает только старую реализацию ViewPager. Есть ли способ легко привязать TabLayout к компоненту ViewPager2?


person Ugurcan Yildirim    schedule 27.03.2019    source источник
comment
Возможно, этот образец ViewPager2 от Google поможет: github.com/android/views -widgets-samples / tree / master / ViewPager2   -  person IgorGanapolsky    schedule 04.03.2020


Ответы (9)


Вы должны использовать это TabLayoutMediator, которое имитирует tabLayout.setupWithViewPager() и устанавливает ViewPager2 с Tablayout. В противном случае вам придется написать свой собственный адаптер, который объединит обе стороны.

Это код в котлине будет выглядеть так:

TabLayoutMediator(tabLayout, viewPager) { tab, position ->
  tab.text = tabTitles[position]
}.attach()
person Nikola Despotoski    schedule 29.03.2019
comment
Код для использования этого TabLayoutMediator выглядит следующим образом: TabLayoutMediator(tabLayout, viewPager2) { tab, position -> viewPager2.setCurrentItem(tab.position, true) if (position == 1) tab.setText("Test2") if (position == 0) tab.setText("Test1") }.attach() - person taranjeetsapra; 13.04.2019
comment
любой, кто читает это, должен дважды проверить версию библиотеки материалов своего приложения. TabLayoutMediator присутствует в implementation 'com.google.android.material:material:1.1.0-alpha08' , но студия Android по умолчанию импортировала версию 1.0.0, которой не было. - person ansh sachdeva; 24.08.2019
comment
Как я могу установить точки на TabLayout (например, карусель) вместо чисел на вкладках? - person IgorGanapolsky; 10.03.2020
comment
viewPager.setCurrentItem(tab.position, true) не нужен и вызывает выбор 2-й вкладки, а страницы всегда пролистываются! - person ericn; 15.05.2020
comment
@ericn, ты прав, брат, установка текущего элемента выполняется сама свайпом. - person Vikas Pandey; 26.08.2020
comment
@IgorGanapolsky Привет, вам просто нужно приложение: tabBackground = @ drawable / tab_selector, а yout tab_selector должен предоставлять разные изображения для выбранного и невыбранного случая - person Vikas Pandey; 26.08.2020
comment
@anshsachdeva Я использую последнюю версию 1.3.0-alpha02, но я не могу найти класс TablayoutMediator - person Vikas Pandey; 20.09.2020
comment
@ericn, если удалить viewPager.setCurrentItem(tab.position, true), будут отображаться вкладки, но у нас будет недостаточно контроля над фрагментом (например, мы не будем фильтровать список в RecyclerView). Итак, это следует добавить. - person CoolMind; 03.02.2021
comment
Если вы вызываете этот метод из onCreateView, убедитесь, что viewPager != null или получите NPE. - person CoolMind; 03.02.2021

ОБНОВИТЬ

проверьте это Создавайте пролистывающие представления с вкладками с помощью ViewPager2

Вот обновленный ответ Как использовать TabLayout с ViewPager2 в Android

Теперь нам не нужно создавать класс из TabLayoutMediator

Используйте ниже dependencies

implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'

КОД ОБРАЗЦА

Макет XMl

<?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">

    <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager"
            app:layout_anchor="@id/tabs"
            app:layout_anchorGravity="bottom"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
    />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

Мероприятия

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.tabs.TabLayoutMediator

import com.google.android.material.tabs.TabLayout


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//        setSupportActionBar(toolbar)
        viewpager.adapter = AppViewPagerAdapter(supportFragmentManager, lifecycle)

        TabLayoutMediator(tabs, viewpager, object : TabLayoutMediator.OnConfigureTabCallback {
            override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
                // Styling each tab here
                tab.text = "Tab $position"
            }
        }).attach()


    }
}

ОБНОВЛЕНИЕ

Если вы используете implementation 'com.google.android.material:material:1.1.0-alpha10', используйте приведенный ниже код

        TabLayoutMediator(tabs, viewpage,
        TabLayoutMediator.TabConfigurationStrategy { tab, position ->
            when (position) {
                0 -> { tab.text = "TAB ONE"}
                1 -> { tab.text = "TAB TWO"}
            }
        }).attach()

ВЫХОД

введите описание изображения здесь

person AskNilesh    schedule 27.07.2019
comment
любой, кто читает это, должен дважды проверить версию библиотеки материалов своего приложения. эта версия работала для меня, но студия Android по умолчанию импортировала версию 1.0.0, в которой этого не было - person ansh sachdeva; 16.08.2019
comment
@anshsachdeva то же самое, я не могу найти класс TabLayoutMediator в com.google.android.material: material: 1.0.0-alpha08, com.google.android.material: material: 1.0.0-alpha09. - person Seyid-Kanan Bagirov; 23.08.2019
comment
@ Seyid-KananBagirov он присутствует в реализации com.google.android.material: material: 1.1.0-alpha08 (обратите внимание на версию 1.1.0-alpha08, а не 1.0.0) - person ansh sachdeva; 24.08.2019
comment
в 1.1.0-alpha10 они переименовали TabLayoutMediator.OnConfigureTabCallback в TabLayoutMediator.TabConfigurationStrategy - person Almighty; 08.09.2019
comment
Я получаю неразрешенную ссылку на AppViewPagerAdapter? - person crmepham; 08.09.2019
comment
@crmepham вам нужно создать класс AppViewPagerAdapter, подобный этому github.com/RathodNilesh14/TabLayout-with-viewpager2-/blob/ - person AskNilesh; 09.09.2019
comment
@Almighty Мне нужно просто переименовать его и все? - person android developer; 19.09.2019
comment
@androiddeveloper да, пожалуйста, проверьте обновленный ответ - person AskNilesh; 19.09.2019
comment
@NileshRathod Спасибо. Почему мы должны указывать TabConfigurationStrategy? Он помечен как ненулевой, но на самом деле мне не нужно ничего делать для обратного вызова. Для меня это пусто ... - person android developer; 19.09.2019
comment
также tabl; ayoutmediatar сейчас не поддерживается, мы должны использовать другой метод для прикрепления tablayout и viewpager 2 - person Vikas Pandey; 07.10.2020
comment
В принятом ответе также есть viewPager.setCurrentItem(tab.position, true). Это дает больше контроля над фрагментами. - person CoolMind; 03.02.2021
comment
новый способ подключения viewpager2 и tablayout также решает проблему, когда мы получим сбой, если по-прежнему будем использовать старый способ подключения viewpager2 и tablayout, когда я включил ProGuard - person Franz Andel; 26.05.2021

Ни хаков, ни расширений, ни TabLayoutMediator

Я использую implementation 'com.google.android.material:material:1.2.0-alpha02' и делаю следующее без использования TabLayoutMediator. Вместо этого я связываю TabLayout с ViewPager2, используя метод, описанный здесь < / а>. Я также добавил рабочий пример в github здесь. Думаю, я свел решение к минимальному рабочему примеру. Я объясню важные моменты.

Добавление элементов в шаблон

Сначала нам нужно добавить в макет TabLayout и ViewPager2. Я разместил их здесь в LinearLayout и CoordinatorLayout, но вы, конечно, можете делать все, что захотите.

<!-- activity_main.xml -->
<?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">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/pager"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

        </LinearLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Подключение адаптера к вьюпейджеру

Таким образом, адаптер отвечает за предоставление правильных фрагментов деятельности. Вам нужно будет расширить FragmentStateAdapter, что я сделал очень просто, как ниже (это частный класс, потому что он объявлен здесь в моем MainActivity.java):

    private class ViewStateAdapter extends FragmentStateAdapter {

        public ViewStateAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
            super(fragmentManager, lifecycle);
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            // Hardcoded in this order, you'll want to use lists and make sure the titles match
            if (position == 0) {
                return new BarFragment();
            }
            return new FooFragment();
        }

        @Override
        public int getItemCount() {
            // Hardcoded, use lists
            return 2;
        }
    }

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

FragmentManager fm = getSupportFragmentManager();
ViewStateAdapter sa = new ViewStateAdapter(fm, getLifecycle());
final ViewPager2 pa = findViewById(R.id.pager);
pa.setAdapter(sa);

Я добавил фрагменты в свой просмотрщик. (Поскольку я жестко запрограммировал фрагменты в своем адаптере, вы должны использовать список и что-то вроде метода addFragment или что-то в этом роде)

TabLayout

Затем с

TabLayout tabLayout = findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setText("Bar"));
tabLayout.addTab(tabLayout.newTab().setText("Foo"));

Я добавляю две вкладки в свой TabLayout, показывая заголовки, но не позволяя мне переключаться на фрагменты.

Подключение TabLayout к адаптеру

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                pa.setCurrentItem(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });

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

Сменить вкладку при смахивании

Наконец, мы возвращаемся, когда пользователь проводит фрагмент, чтобы установить правильный элемент вкладки как выбранный.

pa.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageSelected(int position) {
        tabLayout.selectTab(tabLayout.getTabAt(position));
    }
});
person Potential Guacamole    schedule 28.05.2020
comment
Хорошее решение! Работает как положено. - person CoolMind; 03.02.2021
comment
Хорошая работа. Оцените проект на GitHub. Спасибо! - person portsample; 18.04.2021
comment
Хорошая работа git, - person baponkar; 31.05.2021

Инициализируйте объект TabLayoutMediator с помощью объекта TabLayout, ViewPager2, autoRefresh - логического типа и объекта OnConfigurationChangeCallback.

TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager2, true, new TabLayoutMediator.OnConfigureTabCallback() {
  @Override
  public void onConfigureTab(TabLayout.Tab tab, int position) {
    // position of the current tab and that tab  
  }
});

Наконец, просто вызовите attach() к объекту TabLayoutMediator , чтобы подключить макет вкладки к окну просмотра: -

 tabLayoutMediator.attach();

autoRefresh - ключ, если установлен в true - (по умолчанию установлено значение true)

RECREATES все вкладки tabLayout, если notifyDataSetChanged вызывается в окне просмотра adapter.

Используйте содержимое TabLayoutMediator.java

person Santanu Sur    schedule 15.05.2019

Вы можете использовать функцию расширения Kotlin:

fun TabLayout.setupWithViewPager(viewPager: ViewPager2, labels: List<String>) {

    if (labels.size != viewPager.adapter?.itemCount)
        throw Exception("The size of list and the tab count should be equal!")

    TabLayoutMediator(this, viewPager,
        TabLayoutMediator.TabConfigurationStrategy { tab, position ->
            tab.text = labels[position]
        }).attach()
}

И назовите это:

 tabLayout.setupWithViewPager(viewPager, listOf("Tab A", "Tab B"))
person seyfullah.bilgin    schedule 05.11.2019
comment
Какую зависимость мне нужно добавить, чтобы использовать это расширение? - person Bartek Pacia; 20.01.2020
comment
Нет никакой зависимости, кроме Kotlin, чтобы использовать функции расширения :) - person seyfullah.bilgin; 20.01.2020
comment
Да, знаю. Я имел в виду, какой артефакт Android KTX я должен добавить, чтобы использовать эту функцию расширения. Также у меня нет setupWithViewPager(ViewPager2, List) в наличии - у меня есть только setupWithViewPager(ViewPager) - person Bartek Pacia; 20.01.2020
comment
Вы должны реализовать ViewPager2 (androidx.viewpager2: 1.0.0) и компоненты matarial (com.google.android.material: material: 1.2.0-alpha03) как артефакт, чтобы использовать эту функцию расширения. - person seyfullah.bilgin; 21.01.2020
comment
И я также рекомендую использовать ViewPager2 вместо старого компонента ViewPager. - person seyfullah.bilgin; 21.01.2020
comment
Да, у меня были эти артефакты, а функция расширения была недоступна. Я каким-то образом заставил его работать без использования этой функции расширения. Спасибо, в любом случае. - person Bartek Pacia; 23.01.2020

Если вы кодируете на JAVA. Для viewPager2 можно использовать следующий адаптер:

public class ViewPagerAdapter extends FragmentStateAdapter {
  private static final int CARD_ITEM_SIZE = 10;
  public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
    super(fragmentActivity);
  }
  @NonNull @Override public Fragment createFragment(int position) {
    return CardFragment.newInstance(position);
  }
  @Override public int getItemCount() {
    return CARD_ITEM_SIZE;
  }
}

И в mainActivity вам нужно иметь что-то, встроенное в следующее:

@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    viewPager = findViewById(R.id.view_pager);
    tabLayout = findViewById(R.id.tabs);
    viewPager.setAdapter(new ViewPagerAdapter(this));
    new TabLayoutMediator(tabLayout, viewPager,
        new TabLayoutMediator.TabConfigurationStrategy() {
          @Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
            tab.setText("Tab " + (position + 1));
          }
        }).attach();
  }
}
person Miguel Silva    schedule 07.04.2021
comment
Спасибо за это! Я люблю случайные фрагменты кода без четкого указания, где их разместить, как и следующий парень, но этот ответ был для меня недостающей частью. - person jawsware; 23.06.2021
comment
в порядке. пока ты счастлив. Я счастлив. Что касается очного обучения, мы можем организовать занятие. напишите мне сообщение! - person Miguel Silva; 23.06.2021

Я использую свою реализацию com.google.android.material: material: 1.2.1 и не использую tabLayoutMediator. Для начала https://developer.android.com/jetpack/androidx/migrate/class-mappings изменились для TabLayout в androidX, поэтому обязательно используйте com.google.android.material.tabs.TabLayout в своем операторе импорта. Вот остальная часть моей реализации решения: объявление макета Xml для viewPager и TabLayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/tan_background"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        app:layout_anchor="@id/tabs"
        app:layout_anchorGravity="bottom"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

и файл активности

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set the content of the activity to use the activity_main.xml layout file
        setContentView(layout.activity_main);

        // Find the view pager that will allow the user to swipe between fragments
        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);

        // Create an adapter that knows which fragment should be shown on each page
        SimpleFragmentPagerAdapter adapter = new SimpleFragmentPagerAdapter(this,getSupportFragmentManager());

        // Set the adapter onto the view pager
        viewPager.setAdapter(adapter);

        // Find the tab layout that shows the tabs
        TabLayout tabLayout = findViewById(R.id.tabs);

        // Connect the tab layout with the view pager. This will
        //   1. Update the tab layout when the view pager is swiped
        //   2. Update the view pager when a tab is selected
        //   3. Set the tab layout's tab names with the view pager's adapter's titles
        //      by calling onPageTitle()
        tabLayout.setupWithViewPager(viewPager);
    }
}

Я также использовал настройку адаптера фрагмента в другом классе, где я передал контекст конструктору для разных вкладок

person Kim    schedule 30.09.2020

Если заголовок вашей вкладки происходит от string.xml.
Вы можете объединить fragment с title в List, а затем установить заголовок с помощью TabLayoutMediator. Это поможет вам легко изменить порядок, удалить или добавить новый фрагмент.

class MyFragment : Fragment() {

    override fun onCreateView(...): View? {
        ...

        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = pagerAdapter.getTabTitle(position)
        }.attach()
    }

    private inner class PagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
        val pages = listOf(
            Pair(HomeFragment.newInstance(), R.string.home),
            Pair(GraphFragment.newInstance(), R.string.graph),
            Pair(SettingFragment.newInstance(), R.string.setting),
        )

        override fun createFragment(position: Int): Fragment {
            return pages[position].first
        }

        override fun getItemCount(): Int {
            return pages.count()
        }

        fun getTabTitle(position: Int): String {
            return getString(pages[position].second)
        }
    }
}
person Linh    schedule 29.12.2020

Убедитесь, что вы используете TabLayoutMediator after your set adapter to view_pager2

TabLayoutMediator(tab_layout, view_pager2) { tab, position ->
                tab.text = fragmentList[position].second  // here u can modify you text..
            }.attach()
person abhi    schedule 31.05.2021