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

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) {
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:

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
- Dampness and a large number of bugs (excusable for beta);
- Closeness. RecyclerView is a private field of ViewPager2 , which deprives us of many possibilities: it is impossible to implement swipe-to-dismiss or drag-n-drop ( ItemTouchHelper connects directly to RecyclerView ), you can not redefine ItemAnimator in any way, do not access LayoutManager directly and use RecycledViewPool . However, with the release of new versions of the component, the number of interface methods inherited from RecyclerView is growing (for example, ItemDecoration ), and we can hope to add the missing methods in the future.
pros
- Support for all the advantages of RecyclerView.Adapter : combining elements of different types in one list, adding and removing elements directly during swipe, animated redrawing of the contents of the list when changing;
- Support for the full spectrum of notify ... methods and automatic calculation of changes using DiffUtil ;
- Ease of transition due to the continuity of syntax;
- Support for vertical and horizontal orientation "out of the box";
- RTL support;
- Support ItemDecorator ;
- Support for software scrolling through fakeScrollBy () ;
- Ability to manually set the number of loaded items;
- The ability to use any of the ready-made open-source solutions to reduce boilerplate code , which is inevitable when writing custom RecyclerView.Adapter . For example, EasyAdapter .
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