ViewPager 2 - neue Funktionalität im alten Wrapper

ViewPager ist eine der bekanntesten und am weitesten verbreiteten Komponenten der Android Support Library. Darauf werden die einfachsten Karussells, Onboardings und Schieberegler hergestellt. Im Februar 2019 veröffentlichte das AndroidX-Entwicklungsteam ViewPager2. Schauen wir uns an, was diese Voraussetzungen waren und welche Vorteile die aktualisierte Version der Komponente hat.



ViewPager 2


Zum Zeitpunkt des Schreibens des Beitrags (Juli 2019) ist eine Beta-Version von ViewPager2 verfügbar. Dies bedeutet, dass die unten genannten Probleme behoben und die Funktionalität verbessert und erweitert werden können. Die Entwickler versprechen, in Zukunft Unterstützung für TabLayout hinzuzufügen (obwohl dies nur mit der ersten Version möglich ist), die Leistung des Adapters zu optimieren, viele kleinere Korrekturen vorzunehmen und die Dokumentation fertigzustellen.

Integration


Die Komponente wird nicht mit Standardpaketen geliefert, sondern separat angeschlossen. Fügen Sie dazu dem Abhängigkeitsblock im Gradle-Skript Ihres Moduls die folgende Zeile hinzu:

implementation "androidx.viewpager2:viewpager2:1.0.0-beta02" 

Implementierung


Beginnen wir mit den guten Nachrichten: Der Übergang von der ersten zur zweiten Version ist so einfach wie möglich und läuft auf eine Änderung der Importe hinaus. Die gute alte Syntax wurde nicht berührt: Die Methode getCurrentItem () gibt die aktuelle Seite zurück. Mit ViewPager2.onPageChangeCallback können Sie den Pager- Status abonnieren. Der Adapter wird weiterhin über setAdapter () installiert .



Es lohnt sich, tiefer zu graben, da deutlich wird, dass der erste und der zweite Pager nichts gemeinsam haben, außer Schnittstellen. Die Vertrautheit mit der Implementierung der setAdapter () -Methode lässt keinen Zweifel offen:

 public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); } 

Ja, ViewPager2 ist nur ein Wrapper über RecyclerView . Dies ist einerseits ein großes Plus, andererseits verursacht es Kopfschmerzen. Mit dem Aufkommen von PagerSnapHelper wurde es möglich, RecyclerView als Broschüre zu tarnen . Diese Klasse ändert die Physik der Schriftrolle. Wenn der Benutzer seinen Finger loslässt, berechnet PagerSnapHelper , welches Listenelement der Mittellinie der Liste am nächsten liegt, und richtet es mit einer glatten Animation genau in der Mitte aus. Wenn der Wisch scharf genug war, scrollt die Liste zum nächsten Element, andernfalls kehrt die Animation in ihren ursprünglichen Zustand zurück.

 new PagerSnapHelper().attachToRecyclerView(mRecyclerView); 

Bild
Stellen Sie bei Verwendung von PagerSnapHelper sicher, dass die Breite und Höhe der RecyclerView selbst sowie aller ViewHolders auf MATCH_PARENT festgelegt sind. Andernfalls ist das Verhalten von SnapHelper nicht vorhersehbar. Fehler können an völlig unerwarteten Stellen auftreten. All dies macht die Erstellung eines Karussells aus Elementen geringer Höhe ziemlich zeitaufwändig, obwohl dies möglich ist.

In Anbetracht all der oben genannten Punkte sieht das Widget im Layout folgendermaßen aus:

 <androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> 

Im selben Paket wie ViewPager2 finden wir auch die ScrollEventAdapter- Klasse, mit deren Hilfe die Syntaxkontinuität aufrechterhalten werden kann. ScrollEventAdapter implementiert RecyclerView.OnScrollListener und wandelt Bildlaufereignisse in OnPageChangeCallback- Ereignisse um.

 @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... } 

Jetzt wird OnPageChangeCallback nicht durch eine Schnittstelle dargestellt, sondern durch eine abstrakte Klasse, mit der Sie nur die erforderlichen Methoden überschreiben können (in den meisten Fällen benötigen Sie nur o nPageSelected (Int) , was funktioniert, wenn eine bestimmte Seite ausgewählt ist):

 main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { //do your stuff } } ) 

Eigenschaften


Bemerkenswert ist die Methode setPageTransformer () , die ViewPager2.PageTransformer als Parameter verwendet. Es legt einen Rückruf für jedes Seitenauswahlereignis fest und dient dazu, eine eigene Animation für diese Seite festzulegen. Rückruf erhält die Ansicht der aktuellen Seite und deren Nummer als Eingabe. Das dieser Methode am nächsten liegende Analogon ist der ItemAnimator von RecyclerView .

In neuen Versionen der Bibliothek wurden zwei Implementierungen des Transformators hinzugefügt:

CompositePageTransformer und MarginPageTransformer . Der erste ist für das Kombinieren von Transformatoren verantwortlich, um mehrere Transformationen gleichzeitig auf einen Pager anzuwenden, und der zweite für das Einrücken zwischen Seiten:

Bild

Darüber hinaus unterstützt das neue Widget Orientierungsänderungen: Durch einfaches Aufrufen der setOrientation () -Methode können Sie Ihren Pager in eine vertikale Liste mit Wischen von oben nach unten verwandeln:

 main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL) 

Dies geschieht erneut dank des Übergangs zur RecyclerView : Unter der Haube wird eine Änderung der Ausrichtung des LayoutManager aufgerufen , der für die Anzeige der Listenelemente verantwortlich ist. Es sollte beachtet werden, dass das Delegieren einer großen Anzahl von Aufgaben an andere Klassen der neuen Komponente zugute gekommen ist: Die Auflistung ist viel kompakter und lesbarer geworden.

Dies ist nicht das Ende des Spaßes. In einem Update erhielt ViewPager2 Unterstützung für ItemDecoration : eine Hilfsklasse zum Dekorieren der untergeordneten Ansicht . Dieser Mechanismus kann verwendet werden, um Trennzeichen zwischen Elementen, Rahmen und Zellenhervorhebungen zu zeichnen.

Es gibt bereits viele vorgefertigte Implementierungen von Dekorateuren, da diese seit vielen Jahren erfolgreich bei der Arbeit mit dem üblichen RecyclerView eingesetzt werden . Alle Entwicklungen sind jetzt auf Pager anwendbar. Standardmäßig ist eine Standardimplementierung von Pager-Trennzeichen verfügbar:

 main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) ) 

Zusammen mit dem nächsten Update im Mai 2019 fügte ViewPager2 eine weitere wichtige Methode hinzu: setOffscreenPageLimit (Int) . Er ist dafür verantwortlich, wie viele Elemente rechts und links von der Zentrale im Pager initialisiert werden. Obwohl RecyclerView standardmäßig für das Zwischenspeichern und Anzeigen der Ansicht verantwortlich ist, können Sie mit dieser Methode die gewünschte Anzahl der zu ladenden Elemente explizit festlegen.

Adapter


Der ideologische Nachfolger des ersten Pager-Adapters ist der FragmentStateAdapter : Die Interaktionsschnittstellen und die Klassennamen sind nahezu identisch. Die Änderungen betrafen nur die Benennung einiger Methoden. Wenn früher die abstrakte Funktion getItem (position) implementiert werden musste, um die gewünschte Fragmentinstanz für die angegebene Position zurückzugeben, und diese Benennung auf zwei Arten interpretiert werden konnte, wurde diese Funktion jetzt in createFragment (position) umbenannt . Die Gesamtzahl der Fragmente wird wie zuvor von der Funktion getCount () bereitgestellt .

Von den wichtigen strukturellen Änderungen an der Schnittstelle sollte auch beachtet werden, dass der Adapter nun die Möglichkeit hat, den Lebenszyklus seiner Elemente zu steuern. Daher akzeptiert er zusammen mit dem FragmentManager im Konstruktor ein Lifecycle-Objekt , entweder eine Aktivität oder ein Fragment . Aus Sicherheitsgründen wurden die Methoden saveState () und restoreState () als endgültig deklariert und wegen Vererbung geschlossen.
Die FragmentViewHolder- Klasse ist für das Speichern von Fragmenten in RecyclerView verantwortlich . Die onCreateViewHolder () -Methode von FragmentStateAdapter ruft FragmentViewHolder.create () auf .

 static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); } 

Wenn die onBindViewHolder () -Methode aufgerufen wird , werden der Bezeichner des Elements an der aktuellen Position und der ViewHolder- Bezeichner zugeordnet , um das Fragment weiter daran anzuhängen:

 final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position); //   

Wenn Sie einen Container aus dem ViewHolder an die View- Hierarchie anhängen , wird eine FragmentTransaction ausgeführt, die dem Container ein Fragment hinzufügt:

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... } 

Somit ergeben sich zwei Verwendungszwecke von ViewPager2 : durch Erben der Adapterklasse, entweder direkt von RecyclerView.Adapter oder von FragmentStateAdapter .



Sicherlich werden Sie eine Frage haben: Warum einen zweiten Pager mit Fragmenten und einem Adapter für diese verwenden, wenn es eine normal funktionierende erste Version gibt? ViewPager ist bei der Arbeit mit großen dynamischen Datenlisten alles andere als eine „Silberkugel“. Es eignet sich hervorragend zum Erstellen von Karussells mit statischen Bildern oder Bannern, aber paginierte Newsfeeds mit dem Laden von Werbepostings und Filtern bringen hart unterstützte und hässliche Monster hervor. Früher oder später werden Sie sicherlich auf den brennenden Wunsch stoßen, alles in RecyclerView neu zu schreiben. Jetzt müssen Sie dies nicht mehr tun, da sich der Pager selbst in ihn verwandelt hat und seine leistungsstarken Funktionen für die Arbeit mit dynamischen Listen ausgeliehen hat, während sie in die übliche Syntax eingeschlossen wurden.

Das einzige, was PagerAdapter uns bieten kann , ist die notifyDataSetChanged () -Methode, die den ViewPager zwingt, alle gerenderten Listenelemente neu zu zeichnen. Möglicherweise stellen Sie fest, dass uns niemand daran hindert, eine Liste mit Positionen für vorhandene Elemente zu speichern und POSITION_UNCHANGED von der Methode getItemPosition () für diese zurückzugeben. Diese Lösung kann jedoch nicht als schön bezeichnet werden, sie ist ziemlich umständlich, außerdem ist es schwierig, diese Fälle zu erweitern, in denen sich die Elemente in der Liste ständig ändern und nicht nur nacheinander am Ende hinzugefügt werden. FragmentStateAdapter verfügt über ein vollständiges Arsenal an RecyclerView.Adapter- Methoden, sodass die Logik des Neuzeichnens von Elementen viel flexibler konfiguriert werden kann. Darüber hinaus können Sie zusammen mit dem FragmentStateAdapter DiffUtil verwenden , mit dem Sie die Benachrichtigung über Änderungen fast vollständig automatisieren können.


Achtung! Damit die notify ... -Methoden ordnungsgemäß funktionieren (mit Ausnahme von notifyDataSetChanged ), sollten die Methoden getItemId (Int) und c ontainsItem (Long) neu definiert werden. Dies geschieht, weil die Standardimplementierung nur die Seitenzahl betrachtet und wenn Sie beispielsweise ein neues Element nach dem aktuellen hinzufügen, wird es nicht hinzugefügt, da getItemId unverändert bleibt. Ein Beispiel für das Überschreiben dieser beiden Methoden basierend auf einer Liste von Elementen vom Typ Int :

 override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) } 



Der Hauptgrund für das Erscheinen von ViewPager2 ist die Zurückhaltung, das Rad neu zu erfinden. Einerseits ist das AndroidX- Entwicklungsteam eindeutig bereit, den veralteten ViewPager jetzt aufzugeben, und wird sicherlich nicht in die Erweiterung seiner Funktionalität investieren. Ja und warum? Schließlich weiß RecyclerView bereits alles, was benötigt wird. Auf der anderen Seite wird das Entfernen und Beenden des Supports für eine derart weit verbreitete Komponente offensichtlich nicht zu einer Loyalität der Community führen.

Zusammenfassend: ViewPager2 ist definitiv eine Aufmerksamkeit wert, obwohl es im Moment nicht ohne schwerwiegende Mängel ist.

Nachteile


  • Feuchtigkeit und eine große Anzahl von Fehlern (entschuldbar für die Beta-Version);
  • Nähe. RecyclerView ist ein privates Feld von ViewPager2 , das uns viele Möglichkeiten nimmt: Es ist unmöglich, Swipe-to- Dism oder Drag- and -Drop zu implementieren ( ItemTouchHelper stellt eine direkte Verbindung zu RecyclerView her ). Sie können ItemAnimator in keiner Weise neu definieren , nicht direkt auf LayoutManager zugreifen und RecycledViewPool verwenden . Mit der Veröffentlichung neuer Versionen der Komponente wächst jedoch die Anzahl der von RecyclerView geerbten Schnittstellenmethoden (z. B. ItemDecoration ), und wir können hoffen, die fehlenden Methoden in Zukunft hinzufügen zu können.

Vorteile


  • Unterstützung für alle Vorteile von RecyclerView.Adapter : Kombinieren von Elementen unterschiedlichen Typs in einer Liste, Hinzufügen und Entfernen von Elementen direkt während des Wischens, animiertes Neuzeichnen des Inhalts der Liste beim Ändern;
  • Unterstützung für das gesamte Spektrum der Benachrichtigungsmethoden und automatische Berechnung von Änderungen mit DiffUtil ;
  • Einfacher Übergang aufgrund der Kontinuität der Syntax;
  • Unterstützung für vertikale und horizontale Ausrichtung "out of the box";
  • RTL- Unterstützung;
  • Support ItemDecorator ;
  • Unterstützung für das Scrollen von Software durch fakeScrollBy () ;
  • Möglichkeit, die Anzahl der geladenen Elemente manuell festzulegen;
  • Die Möglichkeit, eine der vorgefertigten Open-Source-Lösungen zu verwenden, um den Boilerplate-Code zu reduzieren, was beim Schreiben von benutzerdefiniertem RecyclerView.Adapter unvermeidlich ist. Zum Beispiel EasyAdapter .

Zusammenfassend möchte ich sagen, dass ViewPager2 wirklich einen genaueren Blick wert ist. Dies ist eine vielversprechende, erweiterbare und funktionale Lösung. Und obwohl es zu früh ist, ein neues Widget in der Produktion zu starten, kann man mit Sicherheit sagen, dass es nach einer vollständigen Veröffentlichung seinen Vorfahren vollständig ersetzen kann und sollte.

Für diejenigen, die es wagen und entschlossen sind, die der Artikel zum Experimentieren inspiriert hat, erschien PagerSnapHelper in der 28. Version der Support-Bibliothek. Dies bedeutet, dass Sie es zusammen mit Ihrem RecyclerView verwenden können , indem Sie ViewPager2 selbst erstellen .

Die Beispieloperation von ViewPager2 und FragmentStateAdapter .

Offizielle Versionshinweise ViewPager2

Source: https://habr.com/ru/post/de461873/


All Articles