ViewPager2 in Android: Getting Started
In this tutorial for Android, you’ll learn to use the new ViewPager2 widget. Along the way you will build an app to show celebrity doppelganger animals. By Rajdeep Singh.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
ViewPager2 in Android: Getting Started
20 mins
- Getting Started
- Project Structure
- Introducing ViewPager2
- What’s New Since ViewPager
- Implementing a Basic Swipeable Screen
- Adding the ViewPager2 Widget
- Wiring Your ViewPager2 With a FragmentStateAdapter
- Under the Hood of FragmentStateAdapter
- Overriding onCreateViewHolder
- Overriding onBindViewHolder
- Listening for Page Changes
- Trying the Vertical Swipe
- Connecting a TabLayout With ViewPager2
- Using TabsLayoutMediator
- Trying the RTL Support
- Where to Go From Here?
If you’ve used Android apps for some time, you’ve likely noticed many apps feature a common swipeable screen pattern. During the onboarding process, many apps use this pattern to showcase their major features one at a time. Another popular use case is using swipeable tabs to show different sections of the app.
Most developers use the ViewPager widget to develop these screens. On November 20, 2019, Google announced the stable version of ViewPager2, which replaces the old ViewPager and fixes many of its weaknesses.
In this tutorial, you’ll build Animal Doppleganger, an app that shows celebrity doppelganger animals, and learn:
- How ViewPager2 works and how it differs from the previous ViewPager.
- How to use ViewPager2 with Fragments.
- What goes on under the hood of
FragmentStateAdapter
, a replacement forFragmentStatePagerAdapter
. - How to implement vertical swiping with ViewPager2.
Getting Started
Click the Download Materials button at the top or bottom of the page to download the starter and final projects.
Now, open Android Studio and click Open an existing Android Studio project.
Navigate to the starter project directory you downloaded and click Open.
Take some time to familiarize yourself with the code.
Project Structure
You’ll find the following files in the starter project:
- MainActivity.kt is the main class for this tutorial. You’ll add ViewPager2 related code primarily in this class.
-
DoppelgangerFragment.kt is a class you’ll use to add doppelganger images in your app. It extends
Fragment
and shows different images based on the value passed in itsgetInstance(position: Int)
method. - A directory named doppelgangers inside the assets directory holds six photos named 0 to 5 with the .jpg extension.
Now that you have an overview of the files in this project, build and run the app. You’ll see a screen like this:
Introducing ViewPager2
Google announced ViewPager2 around Google IO/19 as the successor to the existing ViewPager. As of November 2019, a stable version of ViewPager2 is finally available. To start using this new component, you have to migrate to AndroidX, as it’s not available in the support library.
According to the official Google documentation, ViewPager2 is addressing most of its predecessor’s shortcomings, including right-to-left layout support, vertical orientation and modifiable Fragment collections. You’ll explore many of these features while working on the sample project.
What’s New Since ViewPager
Your app’s users may not notice much change apart from the smoother performance, but, as an app developer, you should notice many improvements.
ViewPager2 uses RecyclerView, one of the most used widgets in the Android world, to show collections of items. This is a huge change for the ViewPager API, as RecyclerView is a powerful and robust widget.
Using all of RecyclerView’s benefits, ViewPager 2 provides these improvements:
- It supports vertical paging by using
LinearLayoutManager
. This means you can easily switch between horizontal and vertical orientations. - It provides support for right-to-left, or RTL, layouts.
- It supports the use of a DiffUtil by default, since it’s a RecyclerView. This lets you dynamically add Fragments or Views and then notify the ViewPager2 to update the UI.
Enough with the theory, time to start coding!
Implementing a Basic Swipeable Screen
In this section, you’ll modify the starter project to show swipeable sections of animal doppelgangers instead of showing only Doguel Jackson.
This requires the following:
- Adding the dependency for
androidx.viewpager2:viewpager2
in the app level build.gradle. - Adding the ViewPager2 XML widget in the layout file.
- Wiring the ViewPager2 with custom
FragmentStateAdapter
.
Adding the ViewPager2 Widget
In the starter project, select the Android view of the project files if not already selected.
Now, open build.gradle (Module: app) and add the following below the line marking TODO:1
:
implementation 'androidx.viewpager2:viewpager2:1.0.0'
The code above tells Gradle that your app module depends on androidx.viewpager2:viewpager2. With the line added, sync Gradle by clicking the Sync Now button that shows up at the top of the file. After a successful sync, you can start using ViewPager2 in the project!
Right now, the app shows a single Fragment in MainActivity
. It showcases my all-time favorite Avenger, Doguel Jackson.
Before you can display other famous doppelgangers, you need to remove some existing code. Open MainActivity.kt and remove the following code:
supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, DoppelgangerFragment.getInstance(0)).commit()
Now, open activity_main.xml, which is the layout file of MainActivity
, and remove this code snippet:
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
This Framelayout contained a single instance of the Fragment, but you’ll want to show multiple ones instead!
Build and run the app to make sure you see a blank screen like this:
If your app screen looks as blank as one’s mind tends to be during an important exam, you can start adding the ViewPager2 related code. With activity_main.xml still open, add the following lines below TODO:2
:
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/doppelgangerViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
This adds a ViewPager2 widget in your layout with the ID doppelgangerViewPager
. You’ll use it to refer to this view in your Kotlin code. This view covers the entire screen because of the match_parent
value for both layout_width
and layout_height
.
You won’t see any change if you try to run the app at this point because ViewPager2
on its own is simply a ViewGroup
. The magic happens when you add a data adapter to it, which you’ll do in the next section.
Wiring Your ViewPager2 With a FragmentStateAdapter
In this section, you’ll create a custom FragmentStateAdapter
and wire it with your ViewPager2
widget. You’ll learn more about how exactly FragmentStateAdapter
works later.
Create a new Kotlin file and name it DoppelgangerAdapter. Paste the following code inside:
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
class DoppelgangerAdapter(activity: AppCompatActivity, val itemsCount: Int) :
FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return itemsCount
}
override fun createFragment(position: Int): Fragment {
return DoppelgangerFragment.getInstance(position)
}
}
Here’s a breakdown of the code:
- You declare a class named
DoppelgangerAdapter
which takes two parameters. The first is a reference to theActivity
which hosts the adapter, and the second is anInt
that tells the adapter the number of items it’ll show. The Activity reference is passed to the super constructor. You’ll see why this is required later. - Next, you override
getItemCount()
which is an abstract method that returns the total number of items in the adapter. - Finally, you override
createFragment(position: Int)
, which is again an abstract method and returns a Fragment instance for the given position.DoppelgangerFragment.getInstance(position)
creates instances ofDoppelgangerFragment
with a different doppelganger picture for different values of position.
Here comes the last step before your ViewPager2 starts working. Go to MainActivity.kt and add the following import statement:
import kotlinx.android.synthetic.main.activity_main.*
Then, add the following lines below TODO:3
:
val doppelgangerAdapter = DoppelgangerAdapter(this, doppelgangerNamesArray.size)
doppelgangerViewPager.adapter = doppelgangerAdapter
In the code above, you created an instance of your DoppelgangerAdapter
, passing in the current instance of MainActivity
and the size of doppelgangerNamesArray
, which is the number of pages you want to show.
MainActivity
already had doppelgangerNamesArray
defined, which is an array of strings containing the names of celebrities. It gets the value from strings.xml in res/values.
Give yourself a pat on the back!
Build and run the app and you’ll see Doguel Jackson again. But if you swipe, you’ll see other doppelgangers, too. The app should behave like this:
Under the Hood of FragmentStateAdapter
Did you decide to give it a read? Good! This will be quick and worth your time.
In the last section, you created DoppelgangerAdapter
which extended FragmentStateAdapter
. In this section, you’ll see what goes on inside FragmentStateAdapter
by diving into specific parts of its code.
Open DoppelgangerAdapter
and navigate to the declaration of FragmentStateAdapter
by clicking ⌘ + Click on macOS or Ctrl + Click on Windows. You’ll see the class declaration like this:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
After looking at the code above, you know that ViewPager2 uses RecyclerView.Adapter
with some changes to show views. This means you can extend your adapter directly from this base class in case you want to show instances of View
class like you do for any other RecyclerView
.
So why extend FragmentStateAdapter
at all? It comes with a lot of code to handle Fragment-related stuff for you. Remember the Activity
instance you passed in to its constructor? This is how that instance is used:
public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(),
fragmentActivity.getLifecycle());
}
The adapter stores the FragmentManager
and Lifecycle
of the Activity and uses them to handle adding Fragments in the ViewHolder
and managing them during lifecycle events.
If you’ve used a RecyclerView Adapter before, then you know the two most important methods you have to override: onCreateViewHolder
and onBindViewHolder
.
Overriding onCreateViewHolder
Take a look at onCreateViewHolder
in FragmentStateAdapter
:
@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
It calls create()
on FragmentViewHolder
which is simply a ViewHolder
. Take a look at create()
in FragmentViewHolder
:
@NonNull static FragmentViewHolder create(@NonNull 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);
}
It returns a FragmentViewHolder
instance with a FrameLayout
added as the itemView
.
Overriding onBindViewHolder
The other main part of Adapter is onBindViewHolder
. Below is a extremely simplified version of it:
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
...
ensureFragment(position);
...
gcFragments();
}
This methods performs some sanity checks and then calls ensureFragment(position)
. This method checks if the Fragment exists and, if it doesn’t, it calls the createFragment(position)
you’ve over-riden in your custom adapter.
It then has some code to adapt to layout changes. Next comes another interesting part, where it calls gcFragments()
. It removes the Fragments which are no longer part of the data set or fall in the category of stale Fragments, for which it has checks. This ensures that ViewPager2 is memory efficient with Fragments.
This is a brief look at this adapter, as its many features are beyond the scope of this article. Feel free to explore them on your own and ask questions in the forum if you have any doubts.
Listening for Page Changes
While using ViewPager2, you may want to know when the screen state changes from scrolling to idle or when the user selects a new page on the screen.
If you’ve used ViewPager
, you used the OnPageChangeListener
interface. With OnPageChangeListener
, you have to override all the methods in the interface, whether you want to use them or not.
ViewPager2 provides a similar API to listen to page changes. But, instead of using an interface, it uses an abstract class, OnPageChangeCallback
, which provides an empty implementation for its three methods. This way, you only need to override the methods you need!
Add the following import statements in MainActivity
:
import android.widget.Toast
import androidx.viewpager2.widget.ViewPager2
Next, add the following code below TODO:4
in MainActivity
:
var doppelgangerPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
Toast.makeText(this@MainActivity, "Selected position: ${position}",
Toast.LENGTH_SHORT).show()
}
}
This code uses Kotlin’s object expression to anonymously implement ViewPager2.OnPageChangeCallback
and overrides only onPageSelected(position: Int)
. It then shows a Toast every time a new page is selected.
OnPageChangeCallback
has three methods:
- onPageScrolled(int position, float positionOffset, @Px int positionOffsetPixels): ViewPager2 invokes this method when the currently selected page starts scrolling.
- onPageSelected(int position): When a new page is selected, ViewPager 2 invokes this method.
- onPageScrollStateChanged(@ScrollState int state): ViewPager 2 invokes this when the scroll state of the current page changes.
Now you need to wire your doppelgangerViewPager
with the doppelgangerPageChangeCallback
so it can start to get the callbacks. In MainActivity
, find TODO:5
and add the following:
doppelgangerViewPager.registerOnPageChangeCallback(doppelgangerPageChangeCallback)
You need to unregister the callback when you no longer want to listen to the changes. In onDestroy()
, add the following code below TODO:6
:
doppelgangerViewPager.unregisterOnPageChangeCallback(doppelgangerPageChangeCallback)
Build and run the app. You’ll see a Toast message whenever you swipe the page.
Trying the Vertical Swipe
ViewPager2 comes with an option to switch orientation easily, which was tough to do using the original ViewPager. This is now easy because ViewPager 2 uses a RecyclerView which in turn uses a LayoutManager
to manage the positioning of views inside it.
Paste the following code under TODO:7
in MainActivity.kt:
doppelgangerViewPager.orientation = ViewPager2.ORIENTATION_VERTICAL
This orientation property will change which way the pages swipe. It’s that easy!
Build and run the app. Try to swipe vertically this time.
Look at the implementation of the orientation
keyword by selecting ⌘ + Click on macOS or Ctrl + Click on Windows. Android Studio takes you to the ViewPager2
class where you see the following:
/**
* Sets the orientation of the ViewPager2.
*
* @param orientation {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}
*/
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
mAccessibilityProvider.onSetOrientation();
}
Now you know for sure this method does nothing but call setOrientation
on the underlying LinearLayoutManager
.
Thank you, RecyclerView! :]
Connecting a TabLayout With ViewPager2
Showing tabs whenever you use swipeable screens is a good way to let the users know there’s more to see by swiping. The process of adding tabs has changed a bit with ViewPager2.
Android provides TabLayout
, a widget to show tabs. Earlier it came with the support library but since the migration to AndroidX, it’s part of the com.google.android.material
package.
Open the app level build.gradle again and paste the following code under TODO:8
:
implementation 'com.google.android.material:material:1.2.0-alpha04'
Sync the project with Gradle files. With that in place, you can now use TabLayout
in your project.
Open activity_main.xml and paste the following code under TODO:9
:
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
app:tabMode="scrollable"
app:tabTextColor="@android:color/white" />
Your xml shows a namespace error for app
, which you can fix by adding this line in the parent LinearLayout
. A quick fix should also show up for this. To apply it, select ⌥ + Enter on macOS or Alt + Enter on Windows.
xmlns:app="http://schemas.android.com/apk/res-auto"
Here’s a breakdown of the TabLayout
widget code you added:
- The
android:id="@+id/tabLayout"
attribute sets the ID of theTabLayout
. You’ll use this later in Kotlin to refer to this view. -
layout_width
is set tomatch_parent
andlayout_height
is set towrap_content
. So the tabs cover the full width of the screen and take only as much height as they need. -
android:background="@color/colorPrimary"
sets the background color of the tabs to match the primary color of the app. -
app:tabMode="scrollable"
makes the tabs scrollable in case the number of items in the TabLayout requires more space than available on the screen. Other possible values areauto
andfixed
. Try these on your own later! -
app:tabTextColor="@android:color/white"
sets the text color on the tabs to white.
Using TabsLayoutMediator
Now, here comes the tricky part. To link the TabLayout
and ViewPager2
, you have to use TabLayoutMediator
. This synchronizes the ViewPager and TabLayout to change the position when one gets clicked or swiped.
Take a look at its documentation to learn more about it.
Open MainActivity.kt and comment out the line that sets the orientation
to vertical, as it’ll look better to use horizontal swiping with tabs. Add the following import statement in MainActivity
:
import com.google.android.material.tabs.TabLayoutMediator
Next, add the following code under TODO:10
:
TabLayoutMediator(tabLayout, doppelgangerViewPager) { tab, position ->
//To get the first name of doppelganger celebrities
tab.text = doppelgangerNamesArray[position].split(" ")[0]
}.attach()
Now, if you look at the constructor of TabLayoutMediator
, you’ll understand what’s happening in the above code. This is how it looks:
public TabLayoutMediator(
@NonNull TabLayout tabLayout,
@NonNull ViewPager2 viewPager,
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this(tabLayout, viewPager, true, tabConfigurationStrategy);
}
It expects three parameters. These are a TabLayout
, a ViewPager2
and an interface called TabConfigurationStrategy
, which looks like this:
/**
* A callback interface that must be implemented to set the text and styling of newly created
* tabs.
*/
public interface TabConfigurationStrategy {
/**
* Called to configure the tab for the page at the specified position. Typically calls {@link
* TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied.
*
* @param tab The Tab which should be configured to represent the title of the item at the given
* position in the data set.
* @param position The position of the item within the adapter's data set.
*/
void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
}
Thanks to the beloved Kotlin syntax, if the last parameter to a function is a function, or, in this case, a functional interface, and you’re passing a lambda expression, you can specify it outside the parentheses.
onConfigureTab
is called for each item in the adapter when they are populated. The above code sets the text for each of the tabs with the first name of each doppelganger.
The important thing to notice is attach()
. This method links the functionalities of ViewPager2 and TabLayout together.
Build and run the app. You’ll see the tabs working now.
Trying the RTL Support
RTL stands for right-to-left. In languages like Arabic, where words go from right to left, it’s essential you take measures to ensure your app doesn’t break. Supporting RTL in ViewPager isn’t easy.
However, ViewPager2 comes with RTL support. To try it, you can either force your entire device to RTL mode from Settings or write some Kotlin code to force the tabs and ViewPager2. You’ll try the latter approach.
Add the following import statement in MainActivity
:
import android.view.View
Copy the following code below TODO:11
:
doppelgangerViewPager.layoutDirection = ViewPager2.LAYOUT_DIRECTION_RTL
tabLayout.layoutDirection = View.LAYOUT_DIRECTION_RTL
The code above forces the layout direction of both widgets to RTL. Build and run the app to see the change.
Where to Go From Here?
You can download the final project using the Download Materials button at the top or bottom of this tutorial.
In this tutorial, you learned to use ViewPager2 with Fragments to display animal doppelgangers, connected a TabLayout to it and saw how easy it is to add vertical scrolling and RTL support.
If you want to learn more about ViewPager2, check out this official guide, which shows you how to create custom animations between your pages. You can also take a look at the documentation of the class itself.
If you have any questions or comments, please join the discussion below!