Переход с ViewPager на ViewPager2

ViewPager2 – это улучшенная версия библиотеки ViewPager с расширенными функциональными возможностями. В ней устранены распространенные проблемы, возникающие при использовании ViewPager. Если в вашем приложении уже используется ViewPager, прочитайте эту страницу, чтобы узнать, как перейти на ViewPager2.

Если вы хотите использовать ViewPager2 в своем приложении и в настоящее время не используете ViewPager, ознакомьтесь со статьями Переключение между фрагментами с помощью ViewPager2 и Создание представлений с вкладками с помощью ViewPager2.

Преимущества перехода на ViewPager2

Основная причина перехода заключается в том, что ViewPager2 активно поддерживается, а ViewPager – нет. Однако у ViewPager2 есть и другие преимущества.

Поддержка вертикальной ориентации

ViewPager2 поддерживает вертикальное разбиение на страницы в дополнение к традиционному горизонтальному. Чтобы включить вертикальное разбиение на страницы для элемента ViewPager2, задайте его атрибут android:orientation:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:orientation="vertical" />

Вы также можете задать этот атрибут программно с помощью метода setOrientation().

Поддержка письма справа налево

ViewPager2 поддерживает листание справа налево. Перелистывание справа налево включается автоматически в зависимости от региональных настроек, но вы также можете включить его вручную для элемента ViewPager2, задав атрибут android:layoutDirection:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layoutDirection="rtl" />

Вы также можете задать этот атрибут программно с помощью метода setLayoutDirection().

Изменяемые коллекции фрагментов

ViewPager2 поддерживает разбиение на страницы изменяемой коллекции фрагментов, вызывая notifyDatasetChanged() для обновления пользовательского интерфейса при изменении базовой коллекции.

Это означает, что ваше приложение может динамически изменять коллекцию фрагментов во время выполнения, а ViewPager2 будет корректно отображать измененную коллекцию.

DiffUtil

ViewPager2 основан на RecyclerView, а значит, у него есть доступ к классу утилит DiffUtil. Это дает несколько преимуществ, но самое главное – объекты ViewPager2 могут использовать анимацию изменения набора данных из класса RecyclerView.

Как перенести приложение в ViewPager2

Чтобы обновить объекты ViewPager в приложении до ViewPager2, выполните следующие действия:

Обновите XML-файлы макета

Сначала замените элементы ViewPager в XML-файлах макета на элементы ViewPager2:

<!-- A ViewPager element -->
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- A ViewPager2 element -->
<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Обновите классы адаптеров

При использовании ViewPager вам нужно было расширить класс адаптера, который предоставлял объекту новые страницы. В зависимости от сценария использования ViewPager использовала три разных абстрактных класса. ViewPager2 использует только два абстрактных класса.

Для каждого объекта ViewPager, который вы преобразуете в объект ViewPager2, обновите класс адаптера, чтобы расширить соответствующий абстрактный класс, как показано ниже.

  • Если ViewPager используется PagerAdapter для постраничного просмотра, используйте RecyclerView.Adapter с ViewPager2.
  • Если в ViewPager использовался метод FragmentPagerAdapter для разбиения на страницы небольшого фиксированного количества фрагментов, используйте FragmentStateAdapter с ViewPager2.
  • Если ViewPager использует FragmentStatePagerAdapter для разбивки на страницы большого или неизвестного количества фрагментов, используйте FragmentStateAdapter с ViewPager2.

Параметры конструктора

Классы адаптеров на основе фрагментов, наследующие от FragmentPagerAdapter или FragmentStatePagerAdapter, всегда принимают один объект FragmentManager в качестве параметра конструктора. Если вы расширяете FragmentStateAdapter для класса адаптера ViewPager2, то можете использовать следующие параметры конструктора:

  • Объект FragmentActivity или Fragment, в котором находится объект ViewPager2. В большинстве случаев это лучший вариант.
  • Объект FragmentManager и объект Lifecycle.

Классам адаптеров на основе представлений, которые наследуют непосредственно от RecyclerView.Adapter, не требуется параметр конструктора.

Методы переопределения

В классах адаптера также необходимо переопределить методы для ViewPager2, которые отличаются от методов для ViewPager:

  • Вместо getCount() переопределите getItemCount(). Кроме названия, в этом методе ничего не изменилось.
  • Вместо getItem() переопределите createFragment() в классах адаптеров на основе фрагментов. Убедитесь, что ваш новый метод createFragment() всегда предоставляет новый экземпляр фрагмента при каждом вызове функции, а не использует экземпляры повторно.

Сводка

Чтобы преобразовать класс адаптера ViewPager для использования с ViewPager2, необходимо внести следующие изменения:

  1. Измените надкласс на RecyclerView.Adapter для разбивки на страницы по представлениям или на FragmentStateAdapter для разбивки на страницы по фрагментам.
  2. Измените параметры конструктора в классах адаптеров на основе фрагментов.
  3. Переопределите getItemCount() вместо getCount().
  4. В классах адаптеров на основе фрагментов переопределите createFragment() вместо getItem().

Kotlin

// A simple ViewPager adapter class for paging through fragments
class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    override fun getCount(): Int = NUM_PAGES

    override fun getItem(position: Int): Fragment = ScreenSlidePageFragment()
}

// An equivalent ViewPager2 adapter class
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

Java

// A simple ViewPager adapter class for paging through fragments
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

// An equivalent ViewPager2 adapter class
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
    public ScreenSlidePagerAdapter(FragmentActivity fa) {
        super(fa);
    }

    @Override
    public Fragment createFragment(int position) {
        return new ScreenSlidePageFragment();
    }

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

Рефакторинг интерфейсов TabLayout

В ViewPager2 году в интеграцию с TabLayout будут внесены изменения. Если вы используете ViewPager с объектом TabLayout для показа горизонтальных вкладок навигации, вам нужно провести рефакторинг объекта TabLayout для интеграции с ViewPager2.

TabLayout больше не зависит от ViewPager2 и теперь доступен в составе компонентов Material. Чтобы использовать ее, добавьте в файл build.gradle соответствующую зависимость:

Яркий

implementation "com.google.android.material:material:1.1.0-beta01"

Kotlin

implementation("com.google.android.material:material:1.1.0-beta01")

Вам также нужно изменить расположение элемента TabLayout в иерархии XML-файла макета. В ViewPager элемент TabLayout объявляется как дочерний элемент ViewPager, а в ViewPager2 элемент TabLayout объявляется непосредственно над элементом ViewPager2 на том же уровне:

<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</androidx.viewpager.widget.ViewPager>

<!-- A ViewPager2 element with a TabLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        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="0dp"
        android:layout_weight="1" />

</LinearLayout>

Наконец, необходимо обновить код, который прикрепляет объект TabLayout к объекту ViewPager. Для интеграции с ViewPager в TabLayout используется собственный метод setupWithViewPager(), а для интеграции с ViewPager2 требуется экземпляр TabLayoutMediator.

Объект TabLayoutMediator также обрабатывает задачу создания заголовков страниц для объекта TabLayout, что означает, что классу адаптера не нужно переопределять getPageTitle():

Kotlin

// Integrating TabLayout with ViewPager
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        tabLayout.setupWithViewPager(viewPager)
    }
    ...
}

class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {

    override fun getCount(): Int  = 4

    override fun getPageTitle(position: Int): CharSequence {
        return "OBJECT ${(position + 1)}"
    }
    ...
}

// Integrating TabLayout with ViewPager2
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

Java

// Integrating TabLayout with ViewPager
public class CollectionDemoFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }
    ...
}

public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
    ...
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "OBJECT " + (position + 1);
    }
    ...
}

// Integrating TabLayout with ViewPager2
public class CollectionDemoFragment : Fragment() {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText("OBJECT " + (position + 1))
        ).attach();
    }
    ...
}

Поддержка вложенных прокручиваемых элементов

ViewPager2 не поддерживает вложенные представления прокрутки, если у них та же ориентация, что и у содержащего их объекта ViewPager2. Например, прокрутка не будет работать для вертикального представления прокрутки внутри вертикально ориентированного объекта ViewPager2.

Чтобы поддерживать прокрутку внутри объекта ViewPager2 с той же ориентацией, необходимо вызвать метод requestDisallowInterceptTouchEvent() для объекта ViewPager2, когда вы ожидаете прокрутку вложенного элемента. В примере вложенной прокрутки ViewPager2 показан один из способов решения этой проблемы с помощью универсального специального макета-оболочки.

Дополнительные ресурсы

Дополнительную информацию о ViewPager2 можно найти в следующих ресурсах.

Образцы

Видео