Переход с 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 поддерживает разбивку по страницам справа налево (RTL). Разбиение по страницам RTL включается автоматически, где это необходимо, в зависимости от языкового стандарта, но вы также можете вручную включить разбиение по страницам RTL для элемента 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() в классах адаптеров на основе фрагментов.

Котлин

// 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()
}

Ява

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

Котлин

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 . Хотя TabLayout использует собственный метод setupWithViewPager() для интеграции с ViewPager , для интеграции с ViewPager2 требуется экземпляр TabLayoutMediator .

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

Котлин

// 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()
    }
    ...
}

Ява

// 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 см. в следующих дополнительных ресурсах.

Образцы

Видео