Migracja z ViewPager do ViewPager2

ViewPager2 to ulepszona wersja biblioteki ViewPager, która oferuje rozszerzone funkcje i rozwiązuje typowe problemy z używaniem ViewPager. Jeśli Twoja aplikacja korzysta już z ViewPager, przeczytaj tę stronę, aby dowiedzieć się więcej o migracji do ViewPager2.

Jeśli chcesz używać ViewPager2 w swojej aplikacji, a obecnie nie używasz ViewPager, zapoznaj się z artykułami na temat przesuwania fragmentów między fragmentami za pomocą ViewPager2 i tworzenia widoków przesuwanych z użyciem kart za pomocą ViewPager2, aby dowiedzieć się więcej.

Zalety migracji do ViewPager2

Głównym powodem migracji jest fakt, że ViewPager2 otrzymuje aktywną pomoc w zakresie programowania, a ViewPager nie. ViewPager2 ma jednak też kilka innych dodatkowych zalet.

Obsługa orientacji pionowej

ViewPager2 obsługuje stronicowanie w pionie oprócz tradycyjnego stronicowania w poziomie. Możesz włączyć stronicowanie w pionie w przypadku elementu ViewPager2, ustawiając jego atrybut android:orientation:

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

Możesz też ustawić ten atrybut automatycznie za pomocą metody setOrientation().

Obsługa tekstu od prawej do lewej

ViewPager2 obsługuje stronicowanie od prawej do lewej (RTL). stronicowanie od strony RTL jest włączane automatycznie w stosownych przypadkach na podstawie języka, ale możesz też ręcznie włączyć tę stronę w przypadku elementu ViewPager2, ustawiając jego atrybut android:layoutDirection:

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

Ten atrybut możesz też ustawić automatycznie za pomocą metody setLayoutDirection().

Kolekcje fragmentów z możliwością modyfikacji

ViewPager2 obsługuje stronicowanie przez możliwy do modyfikacji zbiór fragmentów, wywołując notifyDatasetChanged() w celu zaktualizowania interfejsu w przypadku zmiany zbioru fragmentów.

Oznacza to, że aplikacja może dynamicznie modyfikować kolekcję fragmentów w czasie działania, a ViewPager2 prawidłowo wyświetla zmodyfikowaną kolekcję.

Dyfuzor

Usługa ViewPager2 opiera się na platformie RecyclerView, co oznacza, że ma dostęp do klasy narzędzia DiffUtil. Powoduje to kilka korzyści, ale przede wszystkim oznacza, że obiekty ViewPager2 natywnie wykorzystują animację zmian zbioru danych z klasy RecyclerView.

Przenieś swoją aplikację do ViewPager2

Aby zaktualizować obiekty (ViewPager) w aplikacji do wersji ViewPager2, wykonaj te czynności:

Zaktualizuj pliki układu XML

Najpierw zastąp elementy ViewPager w plikach układu XML elementami 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" />

Aktualizowanie klas adaptera

Gdy używasz ViewPager, trzeba było rozszerzyć klasę adaptera, która dostarczyła nowe strony do obiektu. W zależności od przypadku użycia ViewPager wykorzystuje 3 różne klasy abstrakcyjne. ViewPager2 używa tylko 2 klas abstrakcyjnych.

W przypadku każdego obiektu ViewPager, który chcesz przekonwertować na obiekt ViewPager2, zaktualizuj klasę adaptera, aby rozszerzyć odpowiednią klasę abstrakcyjną w następujący sposób:

Parametry konstruktora

Oparte na fragmentach klasy adaptera dziedziczone z FragmentPagerAdapter lub FragmentStatePagerAdapter zawsze akceptują pojedynczy obiekt FragmentManager jako parametr konstruktora. Jeśli rozszerzasz atrybut FragmentStateAdapter dla klasy adaptera ViewPager2, masz zamiast tego dostępne te opcje parametrów konstruktora:

  • Obiekt FragmentActivity lub obiekt Fragment, w którym znajduje się obiekt ViewPager2. W większości przypadków jest to lepsze rozwiązanie.
  • Obiekt FragmentManager i obiekt Lifecycle.

Klasy adaptera oparte na widoku danych dziedziczące bezpośrednio z elementu RecyclerView.Adapter nie wymagają parametru konstruktora.

Metody zastępowania

Klasy adaptera też muszą zastąpić inne metody w przypadku ViewPager2 niż ViewPager:

  • Zamiast getCount() zastąp getItemCount(). Oprócz nazwy ta metoda nie ulegnie zmianie.
  • Zamiast getItem() zastąp createFragment() w klasach adaptacyjnych opartych na fragmentach. Upewnij się, że nowa metoda createFragment() zawsze dostarcza nową instancję fragmentu przy każdym jej wywołaniu, zamiast używać instancji ponownie.

Podsumowanie

Podsumowując, aby przekonwertować klasę adaptera ViewPager na potrzeby ViewPager2, musisz wprowadzić te zmiany:

  1. Zmień klasę nadrzędną na RecyclerView.Adapter w przypadku stronicowania widoków lub na FragmentStateAdapter do stronicowania fragmentów.
  2. Zmieniaj parametry konstruktora w klasach adaptera opartych na fragmentach.
  3. Zastąp getItemCount() zamiast getCount().
  4. Zastąp createFragment() zamiast getItem() w klasach adaptera opartego na fragmentach.

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;
    }
}

Refaktoryzacja interfejsów typu TabLayout

ViewPager2 wprowadza zmiany w integracji z TabLayout. Jeśli do wyświetlania poziomych kart umożliwiających nawigację używasz obecnie obiektu ViewPager z obiektem TabLayout, w celu integracji z ViewPager2 musisz zmodyfikować obiekt TabLayout.

Konto TabLayout zostało odłączone od ViewPager2 i jest teraz dostępne jako część komponentów Material. Oznacza to, że aby go używać, musisz dodać do pliku build.gradle odpowiednią zależność:

Odlotowy

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

Kotlin

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

Musisz też zmienić lokalizację elementu TabLayout w hierarchii pliku układu XML. W przypadku ViewPager element TabLayout jest zadeklarowany jako element podrzędny elementu ViewPager, a w przypadku ViewPager2 element TabLayout jest zadeklarowany bezpośrednio nad elementem ViewPager2, na tym samym poziomie:

<!-- 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>

Na koniec musisz zaktualizować kod, który dołącza obiekt TabLayout do obiektu ViewPager. Chociaż TabLayout do integracji z ViewPager używa własnej metody setupWithViewPager(), do integracji z ViewPager2 wymaga wystąpienia TabLayoutMediator.

Obiekt TabLayoutMediator obsługuje też generowanie tytułów stron dla obiektu TabLayout, co oznacza, że klasa adaptera nie musi zastąpić 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();
    }
    ...
}

Obsługują zagnieżdżone elementy przewijane.

Funkcja ViewPager2 natywnie nie obsługuje zagnieżdżonych widoków przewijania, gdy widok przewijania ma tę samą orientację co obiekt ViewPager2, który go zawiera. Na przykład przewijanie nie zadziała w widoku przewijania w pionie w obiekcie ViewPager2 zorientowanym w pionie.

Aby umożliwić wyświetlanie widoku przewijania w obiekcie ViewPager2 o tej samej orientacji, musisz wywołać w obiekcie ViewPager2 funkcję requestDisallowInterceptTouchEvent(), jeśli zamierzasz zamiast tego przewinąć zagnieżdżony element. Przykład zagnieżdżonego przewijania ViewPager2 pokazuje jeden ze sposobów rozwiązania tego problemu dzięki uniwersalnemu układowi niestandardowego kodu towarzyszącego.

Dodatkowe materiały

Aby dowiedzieć się więcej o usłudze ViewPager2, zapoznaj się z materiałami dodatkowymi poniżej.

Próbki

Filmy