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

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

12 ответов

Решение

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

Ни хаков, ни расширений, ни 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));
    }
});

ОБНОВИТЬ

установите флажок Создавать пролистывающие представления с вкладками с помощью 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()

ВЫХОД

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

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

Наконец, просто позвоните attach() к TabLayoutMediator Объект для соединения табуляции с viewpager: -

 tabLayoutMediator.attach();

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

RECREATES все вкладки tabLayout если notifyDataSetChanged вызывается в viewpager adapter,

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

Вы можете использовать функцию расширения 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"))

это очень просто:

код Java:

      tabLayout=findViewById(R.id.tabLayout);
    viewPager=findViewById(R.id.viewPager);
    viewPager.setAdapter(new ViewPagerAdapter(this));

    new TabLayoutMediator(tabLayout, viewPager, new TabLayoutMediator.TabConfigurationStrategy() {
        @Override
        public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
            if (position==0){
                tab.setText(R.string.photos);
                tab.view.setBackground(getResources().getDrawable(R.drawable.ic_rectangle_1345));
            }
            else {
                tab.setText(R.string.videos);
            }
        }
    }).attach();

    tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            tab.view.setBackground(getResources().getDrawable(R.drawable.ic_rectangle_1345));
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {
            tab.view.setBackgroundColor(Color.TRANSPARENT);
        }

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

        }
    });

адаптер:

      public class ViewPagerAdapter extends FragmentStateAdapter {
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
    super(fragmentActivity);
}

@NonNull
@Override
public Fragment createFragment(int position) {
    if (position==0) {
        return new PhotosFragment();
    }
    else {
        return new VideosFragment();
    }
}

@Override
public int getItemCount() {
    return 2;
}}

XML:

      <com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@color/tool_bar_bg"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/collection_bar" />

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewPager"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tabLayout" />

Если вы кодируете на 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();
  }
}

Готовы ваши макеты, готовы фрагменты и готовы адаптеры. Все, что вам нужно сейчас, это настроить прослушиватель событий на

      tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){

        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            viewPager.setCurrentItem(tab.getPosition());
        }

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

        }

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

        }
    });

Это должно связать макет вкладки с вашим ViewPager2.

Если название вашей вкладки происходит от 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)
        }
    }
}

Я использую свою реализацию 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);
    }
}

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

Вы можете черпать вдохновение из моего примера проекта для создания пользовательских интерфейсов с помощью Viewpager2. я использую TabLayoutMediatorна всех примерах, Простой и очень полезный. https://github.com/gabriel-TheCode/OnboardingViewPagerExamples .

Убедитесь, что вы используете 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()
Другие вопросы по тегам