ViewPager2 Fundamentals
Why ViewPager2 Replaced ViewPager
The original ViewPager had two architectural problems: it only supported horizontal scrolling, and its adapter management was error-prone. ViewPager2 fixes both by:
- Supporting both horizontal and vertical scrolling
- Using RecyclerView's adapter architecture (ListAdapter + DiffUtil)
- Supporting RTL (right-to-left) layouts natively
- Providing better Fragment lifecycle management
Adding ViewPager2
Add the dependency:
dependencies {
implementation("androidx.viewpager2:viewpager2:1.1.0")
}
Basic XML Setup
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
Fragment-Based Pages
ViewPager2 works with Fragments via FragmentStateAdapter. Each page is a Fragment, and the adapter manages their lifecycle:
class OnboardingAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> WelcomeFragment()
1 -> FeaturesFragment()
2 -> GetStartedFragment()
else -> throw IllegalArgumentException("Invalid position: $position")
}
}
}
Set it up in your Fragment:
class OnboardingFragment : Fragment() {
private var _binding: FragmentOnboardingBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentOnboardingBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewPager.adapter = OnboardingAdapter(this)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
RecyclerView-Based Pages
ViewPager2 also supports non-Fragment pages using RecyclerView adapters. Use this when your pages are simple views, not Fragments:
class ImagePagerAdapter(
private val images: List<Int>
) : RecyclerView.Adapter<ImagePagerAdapter.ImageViewHolder>() {
inner class ImageViewHolder(private val imageView: ImageView) :
RecyclerView.ViewHolder(imageView) {
fun bind(resId: Int) {
imageView.setImageResource(resId)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val imageView = ImageView(parent.context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
scaleType = ImageView.ScaleType.CENTER_CROP
}
return ImageViewHolder(imageView)
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
holder.bind(images[position])
}
override fun getItemCount() = images.size
}
Listening to Page Changes
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
// Update UI when page changes (e.g., highlight tab)
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// Animate elements based on scroll position
}
override fun onPageScrollStateChanged(state: Int) {
// SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING
}
})
TabLayout Integration and Page Transformations
TabLayout + ViewPager2
TabLayout provides tab indicators above or below the ViewPager2. Connect them with TabLayoutMediator:
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/tabLayout"
app:layout_constraintBottom_toBottomOf="parent" />
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = when (position) {
0 -> "Home"
1 -> "Search"
2 -> "Profile"
else -> ""
}
// Optional: set tab icon instead of text
// tab.setIcon(R.drawable.ic_home)
}.attach()
Tab Modes
| Mode | Behavior |
|---|---|
TabLayout.MODE_FIXED |
All tabs same width, evenly distributed (2-5 tabs) |
TabLayout.MODE_SCROLLABLE |
Tabs scroll horizontally (5+ tabs) |
Set in XML:
<com.google.android.material.tabs.TabLayout
app:tabMode="scrollable"
app:tabGravity="fill" />
Page Transformations
Animate page transitions with setPageTransformer:
binding.viewPager.setPageTransformer { page, position ->
// Simple depth effect
page.alpha = when {
position < -1 -> 0f
position <= 0 -> 1f
position <= 1 -> 1f - position
else -> 0f
}
// Parallax effect
page.translationX = position * -50
// Scale effect
val scale = 1f - abs(position) * 0.2f
page.scaleX = scale
page.scaleY = scale
}
The position parameter represents the page's relative position: 0 means fully visible, -1 means off-screen left, 1 means off-screen right. Intermediate values represent partially visible pages.
ViewPager2 vs ViewPager vs HorizontalScrollView
| Feature | ViewPager2 | ViewPager | HorizontalScrollView |
|---|---|---|---|
| Vertical scrolling | Yes | No | No |
| RTL support | Yes | No | No |
| Fragment lifecycle | Proper | Fragile | N/A |
| Adapter | RecyclerView-based | Custom | N/A |
| DiffUtil | Built-in | No | N/A |
| Tab integration | TabLayoutMediator | Manual | No |
Always prefer ViewPager2 for new projects. ViewPager is deprecated.
Quiz
1. Why was ViewPager2 created to replace ViewPager?
2. What class connects ViewPager2 with TabLayout?
3. In a PageTransformer, what does a position of 0 mean?
4. What adapter class should you use for Fragment-based pages in ViewPager2?
Flashcards
Question
What are the main improvements of ViewPager2 over ViewPager?
Click to reveal answer
Answer
Vertical scrolling, RTL support, RecyclerView-based adapter (DiffUtil), proper Fragment lifecycle management, and TabLayoutMediator for tab integration.
Question
What does TabLayoutMediator do?
Click to reveal answer
Answer
Connects ViewPager2 with TabLayout, synchronizing tab selection with page changes. It takes a lambda to set tab text or icons based on page position.
Question
What is the difference between TabLayout.MODE_FIXED and MODE_SCROLLABLE?
Click to reveal answer
Answer
MODE_FIXED gives all tabs equal width (best for 2-5 tabs). MODE_SCROLLABLE lets tabs scroll horizontally (best for 5+ tabs).
Question
When should you use a RecyclerView adapter instead of FragmentStateAdapter?
Click to reveal answer
Answer
When pages are simple views (images, text layouts) that don't need Fragment lifecycle management. FragmentStateAdapter is for pages that are Fragments.
Revision Notes
Key Takeaways
- 1. ViewPager2 replaces ViewPager with vertical scrolling, RTL support, and RecyclerView architecture
- 2. Use FragmentStateAdapter for Fragment-based pages and RecyclerView.Adapter for simple view pages
- 3. TabLayoutMediator connects ViewPager2 with TabLayout for synchronized tab navigation
- 4. PageTransformer position 0 means fully visible — use it to animate page transitions
- 5. Always prefer ViewPager2 over the deprecated ViewPager for new projects
Interview Tips
- • Explain why ViewPager2 was created — mention vertical scrolling and RecyclerView architecture
- • Be ready to describe the TabLayoutMediator pattern for tab integration
- • Know the PageTransformer position values: 0 is visible, -1 left, +1 right
- • Discuss when to use FragmentStateAdapter vs RecyclerView.Adapter in ViewPager2
Cheat Sheet
ViewPager2 Cheat Sheet
Setup:
viewPager.adapter = MyFragmentAdapter(this)
TabLayoutMediator(tabLayout, viewPager) { tab, pos ->
tab.text = titles[pos]
}.attach()
FragmentStateAdapter:
- For Fragment-based pages
- Override createFragment() and getItemCount()
- Pass the host Fragment to the constructor
TabLayout modes:
- MODE_FIXED: equal width tabs (2-5)
- MODE_SCROLLABLE: scrollable tabs (5+)
PageTransformer:
- position 0 = fully visible
- position -1 = off-screen left
- position 1 = off-screen right
- Use alpha, translationX, scaleX/Y for effects
Key differences from ViewPager:
- Vertical scrolling
- RTL support
- RecyclerView adapter architecture
- DiffUtil built-in
- ViewPager is deprecated