Hey people! Welcome to my blog. In this article you are going to learn and build how to implement how user can navigate from RecyclerView items to ViewPager2 and swipe between those items.
- ViewPager2 lets you swipe between fragments with or without TabLayout and also swipe items inside RecyclerView in both horizontal and vertical direction.
- In order to implement ViewPager2 in any project, first make sure the project uses AndroidX or-else you have to migrate to AndroidX first, link is provided at the end for migration guide.
- ViewPager2 comes with right-to-left support, orientation changes, now you can use RecyclerView with ViewPager which can enable usage of DiffUtils, LayoutManager, PageTransformations, Fragments as pages which are modifiable etc.
Preview
1. Add dependencies to your project
Start with new android studio project choosing Empty Activity.
Give project and package name and select language Kotlin.
Minimum SDK as API 21: Lollipop and finish
gradle.build(Module: app):
// ConstraintLayout
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// Material Design Components
implementation 'com.google.android.material:material:1.3.0'
// ViewPager2
implementation "androidx.viewpager2:viewpager2:1.0.0"
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.2.0'
2. Add resources to show data in list
We need data such as string resources to show in layout, let’s copy the strings for showing titles and other data, colors, drawables and styles for the app.
Get all the strings and paste into your strings.xml file.
Get all the colors and paste into your colors.xml file.
Get all the styles and paste into your styles.xml file.
Get all drawables and paste into your drawables folder.
Now let’s create a data model class Sports.kt which helps in making a list.
Sports.kt:
import android.content.Context
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
data class Sports(
val id: Int,
val banner: Int,
val title: String,
val subtitle: String,
val about: String
) : Parcelable {
companion object {
fun sportsList(context: Context): List<Sports> {
return listOf(
Sports(0, R.drawable.ic_rugby,
context.getString(R.string.title_rugby),
context.getString(R.string.subtitle_rugby),
context.getString(R.string.about_rugby)
),
Sports(1, R.drawable.ic_cricket,
context.getString(R.string.title_cricket),
context.getString(R.string.subtitle_cricket),
context.getString(R.string.about_cricket)
),
Sports(2, R.drawable.ic_hockey,
context.getString(R.string.title_hockey),
context.getString(R.string.subtitle_hockey),
context.getString(R.string.about_hockey)
),
Sports(3, R.drawable.ic_basketball,
context.getString(R.string.title_basketball),
context.getString(R.string.subtitle_basketball),
context.getString(R.string.about_basketball)
),
Sports(4, R.drawable.ic_volleyball,
context.getString(R.string.title_volleyball),
context.getString(R.string.subtitle_volleyball),
context.getString(R.string.about_volleyball)
),
Sports(5, R.drawable.ic_esports,
context.getString(R.string.title_esports),
context.getString(R.string.subtitle_esports),
context.getString(R.string.about_esports)
),
Sports(6, R.drawable.ic_kabaddi,
context.getString(R.string.title_kabbadi),
context.getString(R.string.subtitle_kabbadi),
context.getString(R.string.about_kabbadi)
),
Sports(7, R.drawable.ic_baseball,
context.getString(R.string.title_baseball),
context.getString(R.string.subtitle_baseball),
context.getString(R.string.about_baseball)
),
Sports(8, R.drawable.ic_mma,
context.getString(R.string.title_mma),
context.getString(R.string.subtitle_mma),
context.getString(R.string.about_mma)
),
Sports(9, R.drawable.ic_soccer,
context.getString(R.string.title_soccer),
context.getString(R.string.subtitle_soccer),
context.getString(R.string.about_soccer)
),
Sports(10, R.drawable.ic_handball,
context.getString(R.string.title_handball),
context.getString(R.string.subtitle_handball),
context.getString(R.string.about_handball)
),
Sports(11, R.drawable.ic_tennis,
context.getString(R.string.title_tennis),
context.getString(R.string.subtitle_tennis),
context.getString(R.string.about_tennis)
)
)
}
}
}
Create item_list layout file to show each item in list with adapter.
item_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
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="wrap_content"
app:cardCornerRadius="0dp"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/banner_item_image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="16dp"
android:scaleType="fitCenter"
app:layout_constraintDimensionRatio="2:2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_hockey" />
<TextView
android:id="@+id/title_item_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="?attr/colorOnSurface"
app:layout_constraintBottom_toTopOf="@id/subtitle_item_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/banner_item_image_view"
tools:text="Title" />
<TextView
android:id="@+id/subtitle_item_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center"
android:textColor="?attr/colorOnSurface"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_item_text_view"
tools:text="Subtitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
Finally create an RecyclerView adapter for fragment to show a list.
SportsAdapter.kt :
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class SportsAdapter(
private val sportsList: List<Sports>,
private val onClickListener: OnClickListener
) :
RecyclerView.Adapter<SportsAdapter.SportsViewHolder>() {
class SportsViewHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
private val banner: ImageView = itemView.findViewById(R.id.banner_item_image_view)
private val title: TextView = itemView.findViewById(R.id.title_item_text_view)
private val subtitle: TextView = itemView.findViewById(R.id.subtitle_item_text_view)
fun bind(
sports: Sports,
onClickListener: OnClickListener
) {
banner.setImageResource(sports.banner)
title.text = sports.title
subtitle.text = sports.subtitle
itemView.setOnClickListener {
onClickListener.onClick(sports)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SportsViewHolder {
return SportsViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_list, parent, false
)
)
}
override fun onBindViewHolder(holder: SportsViewHolder, position: Int) {
val sports: Sports = sportsList[position]
holder.bind(sports, onClickListener)
}
override fun getItemCount() = sportsList.size
class OnClickListener(val clickListener: (sports: Sports) -> Unit) {
fun onClick(sports: Sports) = clickListener(sports)
}
}
3. Add RecyclerView widget to MainActivity
With the model class, adapter and item layout we proceed to show data in MainActivity class.
Since our app theme style is NoActionBar we add our own widget for Toolbar, add this below code to MainActivity layout and class to show RecyclerView.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/recycler_view">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:title="@string/list_title"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Title"
app:titleTextColor="?attr/colorOnPrimary" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:spanCount="2"
tools:itemCount="6"
tools:listitem="@layout/item_list" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
MainActivity.kt:
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sportsList: List<Sports> = Sports.sportsList(applicationContext)
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
val adapter = SportsAdapter(sportsList, sportsItemListener)
recyclerView.adapter = adapter
}
private val sportsItemListener = SportsAdapter.OnClickListener { sports ->
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("Intent to Detail Activity", sports)
startActivity(intent)
}
}
We have added ClickListener in MainActivity which sends to DetailActivity with intent and also takes data which is Sports model class.
Let’s create DetailActivity for showing ViewPager2.
4. Create DetailActivity for ViewPager2
activity_detail.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/detail_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>
ViewPager2 is the only widget inside layout and items will be shown with ViewPager RecyclerView which enables to swipe inside the layout.
Let’s attach the ViewPager to our activity and then create adapter for our ViewPager.
DetailActivity.xml:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewpager2.widget.ViewPager2
class DetailActivity : AppCompatActivity() {
private lateinit var viewPager2: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
val sports: Sports = intent.getParcelableExtra("Intent to Detail Activity")!!
val sportsList: List<Sports> = Sports.sportsList(applicationContext)
viewPager2 = findViewById(R.id.detail_view_pager)
val viewPagerAdapter = DetailViewPagerAdapter(sportsList, this)
viewPager2.adapter = viewPagerAdapter
viewPager2.setCurrentItem(sports.id, false)
}
}
Using setCurrentItem( ) we can call which ever item we want. When we click 3rd item in MainActivity then item is set to directly visible in MainActivity when user opens.
setCurrentItem(Position, SmoothScroll)
If you pass value as true for SmoothScroll then user will be taken to 3rd item which scrolls from first position to 3rd with visible animation.
5. Create ViewPager adapter
Create Adapter and item layout for DetailActivity for ViewPager to show it’s items using RecyclerView
DetailViewPagerAdapter.kt:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.RecyclerView
class DetailViewPagerAdapter(
private val sportsList: List<Sports>,
private val activity: DetailActivity
) : RecyclerView.Adapter<DetailViewPagerAdapter.DetailViewHolder>() {
class DetailViewHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
val toolbar: Toolbar = itemView.findViewById(R.id.detail_toolbar)
private val banner: ImageView = itemView.findViewById(R.id.detail_image_view)
private val title: TextView = itemView.findViewById(R.id.title_detail_text_view)
private val subtitle: TextView = itemView.findViewById(R.id.subtitle_detail_text_view)
private val about: TextView = itemView.findViewById(R.id.about_detail_text_view)
fun bind(
sport: Sports
) {
banner.setImageResource(sport.banner)
title.text = sport.title
subtitle.text = sport.subtitle
about.text = sport.about
toolbar.title = sport.title
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
return DetailViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_detail_adapter, parent, false
)
)
}
override fun onBindViewHolder(holder: DetailViewHolder, position: Int) {
val sportsArgs: Sports = sportsList[position]
holder.bind(sportsArgs)
holder.toolbar.setNavigationOnClickListener {
activity.finish()
}
}
override fun getItemCount() = sportsList.size
}
Adapter class now requires a item layout and views to show one item for user at once. Let’s create layout xml file for items.
item_detail_adapter.xml:
<androidx.coordinatorlayout.widget.CoordinatorLayout
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"
android:fitsSystemWindows="true"
tools:context=".DetailActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/detail_view_scroll_view">
<androidx.appcompat.widget.Toolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_up_button"
app:titleTextAppearance="@style/TextAppearance.Toolbar.DetailTitle" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/detail_view_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<ImageView
android:id="@+id/detail_image_view"
android:layout_width="match_parent"
android:layout_height="256dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_hockey" />
<TextView
android:id="@+id/subtitle_detail_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?attr/colorSecondaryVariant"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/detail_image_view"
tools:text="Subtitle" />
<TextView
android:id="@+id/title_detail_text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="12dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitle_detail_text_view"
tools:text="Title" />
<TextView
android:id="@+id/about_detail_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="?attr/colorOnPrimary"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_detail_text_view"
tools:text="@string/about_tennis" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@android:drawable/ic_menu_search"
app:rippleColor="?attr/colorOnSecondary" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
6. Material ViewPager2
Since ViewPager2 makes use of RecyclerView as adapter to show items we can customize it’s performance and design in many ways.
We can make use of DiffUtils, layout direction such as right-to left, both vertical and horizontal direction to show items, PageTransformations etc.
Below i have added two material changes to the ViewPager2 which you can add to the previous classes with less changes.
Sample PageTransformers are available in official Android Documentation here .
Create a kotlin file for simple PageTransformer from below code.
ZoomOutPageTransformer.kt:
import android.view.View
import androidx.viewpager2.widget.ViewPager2
import kotlin.math.abs
import kotlin.math.max
private const val MIN_SCALE = 0.85f
private const val MIN_ALPHA = 0.5f
class ZoomOutPageTransformer : ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) {
view.apply {
val pageWidth = width
val pageHeight = height
when {
position < -1 -> { // [-Infinity,-1)
// This page is way off-screen to the left.
alpha = 0f
}
position <= 1 -> { // [-1,1]
// Modify the default slide transition to shrink the page as well
val scaleFactor = max(MIN_SCALE, 1 - abs(position))
val vertMargin = pageHeight * (1 - scaleFactor) / 2
val horzMargin = pageWidth * (1 - scaleFactor) / 2
translationX = if (position < 0) {
horzMargin - vertMargin / 2
} else {
horzMargin + vertMargin / 2
}
// Scale the page down (between MIN_SCALE and 1)
scaleX = scaleFactor
scaleY = scaleFactor
// Fade the page relative to its size.
alpha = (MIN_ALPHA +
(((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
}
else -> { // (1,+Infinity]
// This page is way off-screen to the right.
alpha = 0f
}
}
}
}
}
DetailActivity.kt:
viewPager2 = findViewById(R.id.detail_view_pager) viewPager2.setPageTransformer(ZoomOutPageTransformer())
If you check the swipe between items you will notice pages is being zoomed out while user swipes, whereas before the swipe used to be horizontal. You can check the video on top i have added to see how it looks.
One more change i have added is hiding the views while user swipes the items. For this we need a Callback listener for ViewPager to listen when user swipes them.
Hide Fab and Up-button
Since the fab and up-button views are only accessible from adapter class we shall make those changes over there.
In DetailActivity class we need to pass two more arguments to adapter class which is activity reference and viewpager2, so let’s make those changes.
DetailActivity.kt:
val viewPagerAdapter = DetailViewPagerAdapter(sportsList, this, viewPager2)
Run the app and check swipe you will see views will be hidden while scrolling, this same preview also shown in above video. You can make more changes for items similarly.
7. External links, project Github and download
- Migrate to ViewPager2 from ViewPager – Android Developers Documentation
- ViewPager2 release notes and dependencies
- More samples in GitHub by Android Developers
- MaterialDesign for tabs ViewPager2
That’s it guys! Thanks for the read. If you found difficulty in following along with me you can download the project from my GitHub or from link directly I have added below. More useful articles are coming soon so subscribe now.
If you want me to write article on other android components then let me know through contact form. Until then Happy Trails.

Rajasekhar K E
Hi ! I’m Rajasekhar a Programmer who does Android Development, Creative & Technical writing, Kotlin enthusiast and Engineering graduate. I learn from Open Source and always happy to assist others with my work. I spend most of time Training, Assisting & Mentoring students who are absolute Beginners in android development. I’m also running my startup named Developers Breach which mostly works on contributing to open source.
Here We Go Again : (
if (article == helpful) {
println("Like and subscribe to blog newsletter.")
} else {
println("Let me know what i should blog on.")
}
You must be logged in to post a comment.