📜 ⬆️ ⬇️

ViewPager 2 - new functionality in the old wrapper

ViewPager is one of the most famous and widely used components of the Android Support Library. All the simplest carousels, onboardings and sliders are made on it. In February 2019, the AndroidX development team released ViewPager2. Let's look at what these prerequisites were and what advantages the updated version of the component has.



ViewPager 2


At the time of writing this post (July 2019), a beta version of ViewPager2 is available , which means that the problems mentioned below can be fixed and the functionality improved and expanded. The developers promise in the future to add support for TabLayout (while it can only work with the first version), optimize the adapter’s performance, make many minor corrections and finalize the documentation.

Integration


The component is not supplied with standard packages, but is connected separately. To do this, add the following line to the dependencies block in your module’s gradle script:
')
implementation "androidx.viewpager2:viewpager2:1.0.0-beta02" 

Implementation


Let's start with the good news: the transition from the first to the second version is as simple as possible and boils down to a change in imports. The good old syntax was not touched: the getCurrentItem () method returns the current page, ViewPager2.onPageChangeCallback allows you to subscribe to the pager state , the adapter is still installed via setAdapter ().



It is worth digging deeper, as it becomes clear that the first and second pagers have nothing in common except interfaces. Familiarity with the implementation of the setAdapter () method leaves no room for doubt:

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

Yes, ViewPager2 is just a wrapper over RecyclerView . On the one hand, this is a big plus, on the other - it adds a headache. Disguising RecyclerView as a leaflet became possible with the advent of PagerSnapHelper . This class changes the physics of scroll. When the user releases his finger, PagerSnapHelper calculates which element of the list is closest to the centerline of the list, and with smooth animation aligns it exactly in the center. Thus, if the swipe was sharp enough, the list scrolls to the next element, otherwise - with the animation returns to its original state.

 new PagerSnapHelper().attachToRecyclerView(mRecyclerView); 

image
When using PagerSnapHelper, make sure that the width and height of the RecyclerView itself, as well as all its ViewHolders, are set to MATCH_PARENT. Otherwise, the behavior of SnapHelper will be unpredictable, bugs may occur in completely unexpected places. All this makes the creation of a carousel of elements of small height rather time-consuming, although possible.

Given all of the above, in the layout the widget will look like this:

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

In the same package as ViewPager2, we can also find the ScrollEventAdapter class, which helps maintain syntax continuity. ScrollEventAdapter implements RecyclerView.OnScrollListener and transforms scroll events into OnPageChangeCallback events.

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

Now OnPageChangeCallback is represented not by an interface, but by an abstract class, which allows you to override only the necessary methods (in most cases, you only need o nPageSelected (Int) , which works when a specific page is selected):

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

Features


Noteworthy is the setPageTransformer () method, which takes ViewPager2.PageTransformer as a parameter. It sets a callback for each page selection event and serves to set its own animation for this page. Callback receives the View of the current page and its number as input. The closest analogue to this method is the ItemAnimator from RecyclerView .

In new versions of the library, two implementations of the transformer were added:

CompositePageTransformer and MarginPageTransformer . The first is responsible for combining transformers in order to apply several transformations to one pager at once, and the second for indenting between pages:

image

In addition, the new widget supports orientation changes: by simply calling the setOrientation () method, you can turn your pager into a vertical list with swipes from top to bottom:

 main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL) 

This happens again due to the transition to the RecyclerView : under the hood, a change of orientation of the LayoutManager is called , which is responsible for displaying the list items. It should be noted that delegating a large number of tasks to other classes has benefited the new component: its listing has become much more compact and readable.

This is not the end of the fun. In one update, ViewPager2 received support for ItemDecoration : a helper class for decorating child View . This mechanism can be used to draw separators between elements, borders, cell highlighting.

There are already a lot of ready-made implementations of decorators, because for many years they have been successfully used when working with the usual RecyclerView . All developments are now applicable to pagers. Out of the box, a standard implementation of pager element separators is available:

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

Along with the next update in May 2019, ViewPager2 added another important method: setOffscreenPageLimit (Int) . He is responsible for how many elements to the right and left of the central will be initialized in the pager. Although RecyclerView is responsible for caching and displaying the View by default, using this method you can explicitly set the desired number of items to load.

Adapter


The ideological successor of the first pager adapter is the FragmentStateAdapter : the interaction interfaces and class naming are almost the same. The changes affected only the naming of some methods. If earlier it was necessary to implement the abstract function getItem (position) to return the desired Fragment instance for the given position, and this naming could be interpreted in two ways, now this function has been renamed to createFragment (position) . The total number of fragments is supplied as before by the getCount () function.

Of the important structural changes to the interface, it should also be noted that the adapter now has the ability to control the life cycle of its elements, therefore, together with the FragmentManager in the constructor, it accepts a Lifecycle object , either an Activity or a Fragment . Because of this, for security, the saveState () and restoreState () methods were declared final, and closed to inheritance.
The FragmentViewHolder class is responsible for storing fragments inside the RecyclerView . The onCreateViewHolder () method of FragmentStateAdapter calls FragmentViewHolder.create () .

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

When the onBindViewHolder () method is called , the identifier of the element at the current position and the ViewHolder identifier are associated , to further attach the fragment to it:

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

And finally, when attaching a container from the ViewHolder to the View hierarchy, a FragmentTransaction is executed, adding a Fragment to the container:

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

Thus, two uses of ViewPager2 emerge : through the inheritance of the adapter class, either directly from RecyclerView.Adapter , or from FragmentStateAdapter .



Surely you will have a question: why use a second pager with Fragments and an adapter for them when there is a normally functioning first version? ViewPager is far from a "silver bullet" when working with large dynamic data lists. It is great for creating carousels with a static set of pictures or banners, but paginated news feeds with loading advertising posts, filtering give birth to hard-supported and ugly monsters. Sooner or later, you will surely come across a burning desire to rewrite everything on RecyclerView . Now you don’t have to do this, because the pager itself turned into it, borrowing its powerful capabilities for working with dynamic lists, while wrapping them in the usual syntax.

The only thing the PagerAdapter can offer us is the notifyDataSetChanged () method, which forces the ViewPager to redraw all rendered list items. You may reasonably notice that no one is stopping us from storing a list of positions for existing elements and returning POSITION_UNCHANGED from the getItemPosition () method for them, that's it. However, this solution cannot be called beautiful, it is rather cumbersome, moreover, it is difficult to extend to those cases when the elements in the list are constantly changing, and not only sequentially added to the end. FragmentStateAdapter has a full arsenal of RecyclerView.Adapter methods, so the logic of redrawing elements can be configured much more flexibly. Moreover, together with the FragmentStateAdapter, you can use DiffUtil , which allows you to almost completely automate the work of notification of changes.


Attention! For the notify ... methods to work correctly (except for notifyDataSetChanged ), the getItemId (Int) and c ontainsItem (Long) methods should be redefined. This is done because the default implementation looks only at the page number, and if, for example, you add a new element after the current one, it will not be added, since getItemId will remain unchanged. An example of overriding these two methods based on a list of elements of type Int :

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



The main reason for the appearance of ViewPager2 is the reluctance to reinvent the wheel. On the one hand, the AndroidX development team is clearly ready to abandon the obsolete ViewPager now and is certainly not going to invest in expanding its functionality. Yes, and why? After all, RecyclerView already knows everything that is needed. On the other hand, removing and discontinuing support for such a widely used component will clearly not add community loyalty.

To summarize: ViewPager2 is definitely worthy of attention, although at the moment it is not without serious flaws.

Minuses



pros



As a summary, I want to say that ViewPager2 is really worth a closer look. This is a promising, extensible, and functional solution. And although it is still too early to launch a new widget in production, it is safe to say that after a full release it can and should completely supplant its ancestor.

For those daring and decisive, whom the article inspired to experiment, PagerSnapHelper appeared in the 28th version of the Support Library , which means that you can use it together with your RecyclerView by creating ViewPager2 yourself .

The sample operation ViewPager2 and FragmentStateAdapter .

Official release-notes ViewPager2

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


All Articles