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:
- Jeśli
ViewPager
używaPagerAdapter
do wyświetleń strony, użyjRecyclerView.Adapter
zViewPager2
. - Gdy
ViewPager
używaFragmentPagerAdapter
do stron w małej, stałej liczbie fragmentów, użyj parametruFragmentStateAdapter
z parametremViewPager2
. - Jeśli do strony z dużą lub nieznaną liczbą fragmentów dyrektywa
ViewPager
używałaFragmentStatePagerAdapter
, użyj parametruFragmentStateAdapter
z wartościąViewPager2
.
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 obiektFragment
, w którym znajduje się obiektViewPager2
. W większości przypadków jest to lepsze rozwiązanie. - Obiekt
FragmentManager
i obiektLifecycle
.
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ąpgetItemCount()
. Oprócz nazwy ta metoda nie ulegnie zmianie. - Zamiast
getItem()
zastąpcreateFragment()
w klasach adaptacyjnych opartych na fragmentach. Upewnij się, że nowa metodacreateFragment()
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:
- Zmień klasę nadrzędną na
RecyclerView.Adapter
w przypadku stronicowania widoków lub naFragmentStateAdapter
do stronicowania fragmentów. - Zmieniaj parametry konstruktora w klasach adaptera opartych na fragmentach.
- Zastąp
getItemCount()
zamiastgetCount()
. - Zastąp
createFragment()
zamiastgetItem()
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
- Przykłady ViewPager2 w GitHubie
Filmy
- Zmiana strony: migracja do ViewPager2 (Android Dev Summit 2019)