ViewPager2 to ulepszona wersja biblioteki ViewPager, która oferuje rozszerzone funkcje i rozwiązuje typowe problemy związane 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 aplikacji, a obecnie nie korzystasz z ViewPager, przeczytaj artykuły Slide between fragments using ViewPager2 (Przesuwanie między fragmentami za pomocą ViewPager2) i Create swipe views with tabs using ViewPager2 (Tworzenie widoków przesuwania z kartami za pomocą ViewPager2), aby dowiedzieć się więcej.
Zalety przejścia na ViewPager2
Głównym powodem migracji jest to, że ViewPager2 jest aktywnie rozwijany, a ViewPager nie. Jednak ViewPager2 oferuje też kilka innych,
bardziej szczegółowych korzyści.
Obsługa orientacji pionowej
ViewPager2 obsługuje stronicowanie w pionie, a także tradycyjne stronicowanie w poziomie. Możesz włączyć stronicowanie pionowe dla 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, używając metody setOrientation().
Obsługa języków pisanych od prawej do lewej
ViewPager2 obsługuje stronicowanie od prawej do lewej. Stronicowanie od prawej do lewej jest włączane automatycznie, gdy jest to odpowiednie dla danego języka, ale możesz też ręcznie włączyć stronicowanie od prawej do lewej dla 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" />
Możesz też ustawić ten atrybut automatycznie, używając metody setLayoutDirection().
Kolekcje fragmentów, które można modyfikować
ViewPager2 obsługuje stronicowanie w modyfikowalnej kolekcji fragmentów, wywołując notifyDatasetChanged() w celu zaktualizowania interfejsu, gdy zmieni się kolekcja bazowa.
Oznacza to, że aplikacja może dynamicznie modyfikować kolekcję fragmentów w czasie działania, a ViewPager2 będzie prawidłowo wyświetlać zmodyfikowaną kolekcję.
DiffUtil
ViewPager2 jest oparty na RecyclerView, co oznacza, że ma dostęp do klasy użytkowej DiffUtil. Daje to kilka korzyści, ale przede wszystkim oznacza, że obiekty ViewPager2 natywnie wykorzystują animacje zmiany zbioru danych z klasy RecyclerView.
Przenoszenie aplikacji do ViewPager2
Aby zaktualizować obiekty ViewPager w aplikacji do wersji ViewPager2, wykonaj te czynności:
Aktualizowanie plików 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 adapterów
W przypadku ViewPager trzeba było rozszerzyć klasę adaptera, która dostarczała nowe strony do obiektu. W zależności od przypadku użycia firma ViewPager korzystała z 3 różnych klas abstrakcyjnych. ViewPager2 używa tylko 2 klas abstrakcyjnych.
W przypadku każdego obiektu ViewPager, który konwertujesz na obiekt ViewPager2, zaktualizuj klasę adaptera, aby rozszerzyć odpowiednią klasę abstrakcyjną w ten sposób:
- Jeśli
ViewPagerużywaPagerAdapterdo przeglądania widoków, użyjRecyclerView.AdapterzViewPager2. - Jeśli
ViewPagerużywaFragmentPagerAdapterdo stronicowania małej, stałej liczby fragmentów, użyjFragmentStateAdapterzViewPager2. - Jeśli
ViewPagerużywaFragmentStatePagerAdapterdo przeglądania dużej lub nieznanej liczby fragmentów, użyjFragmentStateAdapterzViewPager2.
Parametry konstruktora
Klasy adapterów oparte na fragmentach, które dziedziczą z FragmentPagerAdapter lub FragmentStatePagerAdapter, zawsze akceptują pojedynczy obiekt FragmentManager jako parametr konstruktora. Gdy rozszerzasz klasę FragmentStateAdapter na potrzeby klasy adaptera ViewPager2, masz do wyboru te opcje parametrów konstruktora:
- Obiekt
FragmentActivitylubFragment, w którym znajduje się obiektViewPager2. W większości przypadków jest to lepsze rozwiązanie. - Obiekt
FragmentManageri obiektLifecycle.
Klasy adapterów oparte na widoku, które dziedziczą bezpośrednio z RecyclerView.Adapter, nie wymagają parametru konstruktora.
Metody zastępowania
Klasy adaptera muszą też zastępować inne metody w przypadku ViewPager2 niż w przypadku ViewPager:
- Zamiast
getCount()zastąpgetItemCount(). Oprócz nazwy ta metoda nie uległa zmianie. - Zamiast
getItem()zastąpcreateFragment()w klasach adaptera opartych na fragmentach. Upewnij się, że metodacreateFragment()zawsze dostarcza nową instancję fragmentu przy każdym wywołaniu funkcji, zamiast ponownie używać instancji.
Podsumowanie
Podsumowując, aby przekonwertować klasę adaptera ViewPager do użycia z ViewPager2, musisz wprowadzić te zmiany:
- Zmień klasę nadrzędną na
RecyclerView.Adapter, aby stronicować widoki, lub naFragmentStateAdapter, aby stronicować fragmenty. - Zmień parametry konstruktora w klasach adapterów opartych na fragmentach.
- Zastąp
getItemCount()zamiastgetCount(). - Zastąp
createFragment()zamiastgetItem()w klasach adaptera opartych 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 TabLayout
ViewPager2 wprowadza zmiany w integracji z TabLayout. Jeśli obecnie używasz ViewPager z obiektem TabLayout do wyświetlania poziomych kart nawigacyjnych, musisz refaktoryzować obiekt TabLayout, aby zintegrować go z ViewPager2.
Komponent TabLayout został odłączony od ViewPager2 i jest teraz dostępny w ramach komponentów Material Design. Oznacza to, że aby go używać, musisz dodać odpowiednią zależność do pliku build.gradle:
Dynamiczny
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 deklarowany jako element podrzędny elementu ViewPager, ale w przypadku ViewPager2 element TabLayout jest deklarowany 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. TabLayout używa własnej metody setupWithViewPager() do integracji z ViewPager, ale do integracji z ViewPager2 wymaga instancji TabLayoutMediator.
Obiekt TabLayoutMediator obsługuje też zadanie generowania tytułów stron dla obiektu TabLayout, co oznacza, że klasa adaptera nie musi zastępować metody 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ługa zagnieżdżonych elementów przewijanych
ViewPager2 nie obsługuje natywnie zagnieżdżonych widoków przewijania w przypadkach, gdy widok przewijania ma taką samą orientację jak obiekt ViewPager2, który go zawiera. Na przykład przewijanie nie będzie działać w przypadku widoku przewijania pionowego wewnątrz obiektu ViewPager2 o orientacji pionowej.
Aby obsługiwać widok przewijania w obiekcie ViewPager2 o tej samej orientacji, musisz wywołać requestDisallowInterceptTouchEvent() na obiekcie ViewPager2, gdy chcesz przewijać zagnieżdżony element. Przykładowe zagnieżdżone przewijanie ViewPager2 pokazuje, jak rozwiązać ten problem za pomocą uniwersalnego niestandardowego układu opakowania.
Dodatkowe materiały
Więcej informacji o ViewPager2 znajdziesz w tych materiałach.
Filmy
- Turning the Page: Migrating to ViewPager2 (Android Dev Summit 2019)