📋 Screens, Lists और Navigation · Lesson 28
TabLayout और ViewPager2
Swipe होने वाले tabs
TabLayout tab titles बनाता है। ViewPager2 pages रखता है और user को swipe करने देता है। TabLayoutMediator दोनों को sync रखता है।
Step 1: Layout
XML – res/layout/fragment_notes.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<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"
android:layout_weight="1" />
</LinearLayout>Step 2: FragmentStateAdapter
Kotlin
class CoursePagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment = when (position) {
0 -> SyllabusFragment()
1 -> ReviewsFragment()
else -> FaqFragment()
}
}Step 3: TabLayoutMediator
Kotlin
val titles = listOf("Syllabus", "Reviews", "FAQ")
binding.viewPager.adapter = CoursePagerAdapter(this)
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = titles[position]
}.attach()
// listen for page changes
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
Log.d("Tabs", "Now on ${titles[position]}")
}
})असली code: Student Diary tabs
एक ही NoteListFragment class तीनों tabs के लिए अलग filter argument के साथ reuse होती है:
Kotlin – ui/notes/NotesFragment.kt
package com.codingeasily.studentdiary.ui.notes
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.codingeasily.studentdiary.R
import com.codingeasily.studentdiary.databinding.FragmentNotesBinding
import com.codingeasily.studentdiary.ui.NoteFilter
import com.codingeasily.studentdiary.ui.NoteViewModel
import com.google.android.material.tabs.TabLayoutMediator
/** Home screen: 3 tabs (All / Pending / Done) in a ViewPager2 + a search box in the toolbar. */
class NotesFragment : Fragment() {
private var _binding: FragmentNotesBinding? = null
private val binding get() = _binding!!
private val viewModel: NoteViewModel by activityViewModels()
private val tabs = listOf(
NoteFilter.ALL to R.string.tab_all,
NoteFilter.PENDING to R.string.tab_pending,
NoteFilter.DONE to R.string.tab_done
)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentNotesBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun getItemCount() = tabs.size
override fun createFragment(position: Int) = NoteListFragment.newInstance(tabs[position].first)
}
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.setText(tabs[position].second)
}.attach()
// Toolbar menu that belongs to this screen only
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_notes, menu)
val searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.queryHint = getString(R.string.search_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?) = true
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.setSearchQuery(newText.orEmpty())
return true
}
})
}
override fun onMenuItemSelected(menuItem: MenuItem) = false
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
override fun onDestroyView() {
super.onDestroyView()
viewModel.setSearchQuery("")
_binding = null
}
}Icons, badges और modes
Kotlin
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = titles[position]
tab.setIcon(icons[position]) // R.drawable.ic_…
}.attach()
binding.tabLayout.getTabAt(1)?.orCreateBadge?.number = 12 // "Reviews 12"XML – scroll होने वाले कई tabs
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"
app:tabGravity="start" />Views के साथ onboarding slider
ViewPager2 normal RecyclerView adapter भी लेता है — welcome slides के लिए perfect।
Kotlin
data class Slide(val emoji: String, val title: String, val text: String)
class SlideAdapter(private val slides: List<Slide>) : RecyclerView.Adapter<SlideAdapter.VH>() {
class VH(val b: ItemSlideBinding) : RecyclerView.ViewHolder(b.root)
override fun onCreateViewHolder(p: ViewGroup, t: Int) =
VH(ItemSlideBinding.inflate(LayoutInflater.from(p.context), p, false)) // item root: match_parent
override fun onBindViewHolder(h: VH, i: Int) {
h.b.tvEmoji.text = slides[i].emoji
h.b.tvTitle.text = slides[i].title
h.b.tvText.text = slides[i].text
}
override fun getItemCount() = slides.size
}
// Activity
val slides = listOf(
Slide("📝", "Write notes", "Homework, revision and exam notes in one place"),
Slide("⏰", "Never forget", "Get a reminder before every deadline"),
Slide("📊", "Track progress", "See how much you have completed")
)
binding.pager.adapter = SlideAdapter(slides)
TabLayoutMediator(binding.dots, binding.pager) { _, _ -> }.attach() // TabLayout as dots
binding.btnNext.setOnClickListener {
if (binding.pager.currentItem < slides.lastIndex) binding.pager.currentItem++ else finishOnboarding()
}आम गलतियाँ
- Mediator पर
.attach()भूलना → tabs पर titles नहीं दिखते। - ViewPager2 page layouts में
wrap_content→ crash "Pages must fill the whole ViewPager2";match_parentuse करें। - Page fragments के references list में रखना → वे दोबारा बनते हैं; shared ViewModel से बात करें।
सारांश
- TabLayout + ViewPager2 + FragmentStateAdapter + TabLayoutMediator।
- मिलते-जुलते tabs के लिए arguments के साथ एक fragment class reuse करें।
- Sliders और onboarding के लिए RecyclerView adapter वाला ViewPager2।
✅ Practice
तीन tabs (Syllabus, Reviews, FAQ) वाली Course screen बनाइए और review count का badge दिखाइए। 3-slide onboarding screen जोड़िए जो सिर्फ़ पहली बार खुले (SharedPreferences में flag save करें)।
अक्सर पूछे जाने वाले प्रश्न (FAQ)
ViewPager या ViewPager2?
ViewPager2 use करें। यह RecyclerView पर बना है, right-to-left, vertical paging और DiffUtil support करता है, और यही maintained version है।
Fragment के अंदर FragmentStateAdapter को this दें या requireActivity()?
this (parent fragment) दें। तब pages child fragments बनते हैं और parent के साथ साफ़ होते हैं।
Code से कोई खास tab कैसे खोलें?
viewPager.setCurrentItem(index, true) call करें। Mediator की वजह से TabLayout अपने आप follow करता है।
