ViewPager2
At the Google I/O 2019 ViewPager2 was announced, a large step forward compared the the good old ViewPager.
This tutorial goes about ViewPager2, so we are going to skip an introduction about ViewPager and in which use cases you should use it!.
ViewPager2 is part of the AndroidX JetPack library, more information about AndroidX can in this tutorial.
Within ViewPager2 you are no longer forced to use Fragments, There is still an option to use fragments with the FragmentStateAdapter but we won't dive into that part.
We are focussing on a ViewPager2 with RecyclerView and ListAdapter. If you are unfamiliar with the renewed ListAdapter check here for more information.
Update a layout with the widget androidx.viewpager2.widget.ViewPager2, for most use cases the width and height are configured as match_parent. As last step is to set an Adapter the the ViewPager2 like an RecyclerView.
With the ViewPager2 implemented, lets focus on the color_item.xml and ColorAdapter, which extends ListAdapter. The ColorAdapter contains the same logic as a generic RecyclerView adapter, for readability we've added the ColorViewHolder inside the ColorAdapter. As layout we are using a simple color_item.xml with a TextView.
ViewPager2.OnPageChangeCallback
Another feature is the setCurrentItem(int item), with this feature you are able to selected any page to be shown. If you dont want that the user visually noticed that the ViewPager is scrolled the smoothScroll boolean can be set to false. setCurrentItem (int item, boolean smoothScroll).
As we are using the default RecylerView on the background multiple pages can be drawn, so always create onView matchers with specific elements to verify only one view is found.
For swiping left and right we've can easy use onView(withId(R.id.viewpager2)).perform(swipeLeft());
Thank you for reading this article, If you liked the article, help others find this article by sharing it.
Why use a ViewPager2?
When we want to manage multiple screens through swiping for example within TabLayout or to navigate from item A to item B. One of the biggest benefits of the ViewPager2 is that it uses the RecyclerView, one of the most used layouts in Android. By using the RecyclerView we get for support for or right-to-left (RTL), improved performing, support of DiffUtil.Within ViewPager2 you are no longer forced to use Fragments, There is still an option to use fragments with the FragmentStateAdapter but we won't dive into that part.
We are focussing on a ViewPager2 with RecyclerView and ListAdapter. If you are unfamiliar with the renewed ListAdapter check here for more information.

How to implement a ViewPager2?
The ViewPager is part of the AndroidX Jetpack packages, so the first step is to update the build.gradle file androidx.viewpager2:viewpager2:x.y.zUpdate a layout with the widget androidx.viewpager2.widget.ViewPager2, for most use cases the width and height are configured as match_parent. As last step is to set an Adapter the the ViewPager2 like an RecyclerView.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies { | |
... | |
implementation "androidx.viewpager2:viewpager2:1.0.0" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context=".MainActivity"> | |
<androidx.viewpager2.widget.ViewPager2 | |
android:id="@+id/viewpager2" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MainActivity extends AppCompatActivity { | |
private ViewPager2 mViewPager2; | |
private ColorOnPageChangeCallback mColorOnPageChangeCallback; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
initViewPager2(); | |
} | |
private void initViewPager2() { | |
ColorAdapter colorAdapter = new ColorAdapter(); | |
colorAdapter.submitList(getDefaultList()); | |
mViewPager2 = findViewById(R.id.viewpager2); | |
mViewPager2.setAdapter(colorAdapter); | |
mColorOnPageChangeCallback = new ColorOnPageChangeCallback(); | |
mViewPager2.registerOnPageChangeCallback(mColorOnPageChangeCallback); | |
} | |
@Override | |
protected void onDestroy() { | |
super.onDestroy(); | |
mViewPager2.unregisterOnPageChangeCallback(mColorOnPageChangeCallback); | |
} | |
.. | |
} |
With the ViewPager2 implemented, lets focus on the color_item.xml and ColorAdapter, which extends ListAdapter. The ColorAdapter contains the same logic as a generic RecyclerView adapter, for readability we've added the ColorViewHolder inside the ColorAdapter. As layout we are using a simple color_item.xml with a TextView.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:id="@+id/container" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:background="?android:attr/selectableItemBackground" | |
android:paddingLeft="16dp" | |
android:paddingTop="16dp" | |
android:paddingRight="16dp" | |
android:paddingBottom="16dp"> | |
<TextView | |
android:id="@+id/name" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_alignParentTop="true" | |
android:textColor="@android:color/black" | |
android:textSize="16sp" | |
android:textStyle="bold" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ColorAdapter extends ListAdapter<Color, ColorAdapter.ColorViewHolder> { | |
public ColorAdapter() { | |
super(Color.DIFF_CALLBACK); | |
} | |
@NonNull | |
@Override | |
public ColorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | |
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | |
View view = layoutInflater.inflate(R.layout.color_item, parent, false); | |
return new ColorViewHolder(view); | |
} | |
@Override | |
public void onBindViewHolder(@NonNull ColorViewHolder holder, int position) { | |
Color color = getItem(position); | |
if (color != null) { | |
holder.bindTo(color); | |
} | |
} | |
static class ColorViewHolder extends RecyclerView.ViewHolder { | |
private View container; | |
private TextView name; | |
ColorViewHolder(View view) { | |
super(view); | |
container = view.findViewById(R.id.container); | |
name = view.findViewById(R.id.name); | |
} | |
void bindTo(Color color) { | |
container.setBackgroundResource(color.getColor()); | |
name.setText(name.getContext().getResources().getResourceEntryName(color.getColor())); | |
} | |
} | |
} |
Example content
In this example we are using a Color object to show multiple pages inside the ViewPager. In order to implement all features for a ListAdapter also the DIFF_CALLBACK is included within Color.java.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Color { | |
@ColorRes | |
private int color; | |
public static List<Color> getDefaultList() { | |
List<Color> colors = new ArrayList<>(); | |
colors.add(new Color(R.color.green)); | |
colors.add(new Color(R.color.red)); | |
colors.add(new Color(R.color.gray)); | |
colors.add(new Color(R.color.blue)); | |
colors.add(new Color(R.color.yellow)); | |
colors.add(new Color(R.color.pink)); | |
colors.add(new Color(R.color.white)); | |
return colors; | |
} | |
public Color(int color) { | |
this.color = color; | |
} | |
public int getColor() { | |
return color; | |
} | |
static DiffUtil.ItemCallback<Color> DIFF_CALLBACK = new DiffUtil.ItemCallback<Color>() { | |
@Override | |
public boolean areItemsTheSame(@NonNull Color oldItem, @NonNull Color newItem) { | |
return oldItem.color == newItem.color; | |
} | |
@Override | |
public boolean areContentsTheSame(@NonNull Color oldItem, @NonNull Color newItem) { | |
return oldItem.equals(newItem); | |
} | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
... | |
<color name="red">#88FF0000</color> | |
<color name="green">#8800FF00</color> | |
<color name="blue">#880000FF</color> | |
<color name="yellow">#88FFFF00</color> | |
<color name="pink">#88FF00FF</color> | |
<color name="cyan">#8800FFFF</color> | |
<color name="white">#88FFFFFF</color> | |
<color name="gray">#88000000</color> | |
</resources> |
ViewPager2 extras
The ViewPager2 supports a ViewPager2.OnPageChangeCallback in order to keep the activity updated about any page transitions. With an easy registerOnPageChangeCallback you can registern on events. Important to also call unregisterOnPageChangeCallback() when you are no longer interested in the callback events.ViewPager2.OnPageChangeCallback
- onPageScrollStateChanged(): Called the scroll state changes, there are tree states SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING and SCROLL_STATE_SETTLING.
- onPageScrolled(): Called when the page is scrolled, through programmatically or user initiated scroll.
- onPageSelected(): Called when a new page is selected, this page is now the most visible page
Another feature is the setCurrentItem(int item), with this feature you are able to selected any page to be shown. If you dont want that the user visually noticed that the ViewPager is scrolled the smoothScroll boolean can be set to false. setCurrentItem (int item, boolean smoothScroll).
ViewPager2 result
With above implementation the result is a simple ViewPager2 with default animation showing multiple pages with a color.
Espresso and ViewPager2
Last but not least some information about how to test a ViewPager2 with Espresso.As we are using the default RecylerView on the background multiple pages can be drawn, so always create onView matchers with specific elements to verify only one view is found.
For swiping left and right we've can easy use onView(withId(R.id.viewpager2)).perform(swipeLeft());
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
public void mainActivityTest() { | |
onView(allOf( | |
withId(R.id.name), | |
withText("green"))) | |
.check(matches(isDisplayed())); | |
onView(withId(R.id.viewpager2)).perform(swipeLeft()); | |
onView(allOf( | |
withId(R.id.name), | |
withText("red"))) | |
.check(matches(isDisplayed())); | |
} |
Links
With this upgrade to ViewPager2 we've explained the basic ViewPager2, it's up to you to implement this Widget in great apps.Thank you for reading this article, If you liked the article, help others find this article by sharing it.
Reacties
Een reactie posten