如何在Fragment中使用ViewPager与TabLayout?底部导航嵌套滑动实现
我来一步步帮你实现这个需求——底部导航栏切换顶层Fragment,每个顶层Fragment内部通过ViewPager+TabLayout管理子Fragment,这是移动端很常见的架构模式,下面直接上可落地的代码和关键细节:
一、先搞定底部导航栏与顶层Fragment的切换
这部分是基础,先让底部导航能正常切换不同的主Fragment。
1. 主页面布局(activity_main.xml)
用FrameLayout作为主Fragment的容器,搭配BottomNavigationView:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 承载主Fragment的容器 --> <FrameLayout android:id="@+id/main_fragment_container" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <!-- 底部导航栏 --> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/bottom_nav_menu" /> </LinearLayout>
2. 底部导航菜单(res/menu/bottom_nav_menu.xml)
定义几个导航项,比如首页、发现、我的:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/nav_home" android:icon="@drawable/ic_home" android:title="首页" /> <item android:id="@+id/nav_discovery" android:icon="@drawable/ic_discovery" android:title="发现" /> <item android:id="@+id/nav_profile" android:icon="@drawable/ic_profile" android:title="我的" /> </menu>
3. 主Activity的切换逻辑
在MainActivity里处理底部导航的点击事件,切换对应的主Fragment。如果需要保留Fragment状态,推荐用add+隐藏显示的方式,而不是replace:
class MainActivity : AppCompatActivity() { // 提前初始化所有主Fragment,避免重复创建 private val homeFragment = HomeMainFragment() private val discoveryFragment = DiscoveryMainFragment() private val profileFragment = ProfileMainFragment() private var currentFragment: Fragment = homeFragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化所有主Fragment,默认显示首页 supportFragmentManager.beginTransaction() .add(R.id.main_fragment_container, homeFragment) .add(R.id.main_fragment_container, discoveryFragment) .add(R.id.main_fragment_container, profileFragment) .hide(discoveryFragment) .hide(profileFragment) .commit() // 底部导航点击事件 findViewById<BottomNavigationView>(R.id.bottom_nav).setOnItemSelectedListener { item -> when (item.itemId) { R.id.nav_home -> switchFragment(currentFragment, homeFragment) R.id.nav_discovery -> switchFragment(currentFragment, discoveryFragment) R.id.nav_profile -> switchFragment(currentFragment, profileFragment) else -> false } } } // 切换Fragment的工具方法 private fun switchFragment(from: Fragment, to: Fragment): Boolean { if (currentFragment != to) { currentFragment = to supportFragmentManager.beginTransaction() .hide(from) .show(to) .commit() } return true } }
二、在主Fragment内部实现ViewPager+TabLayout切换子Fragment
这是核心部分,每个主Fragment(比如HomeMainFragment)内部嵌套自己的ViewPager和TabLayout,用来切换子Fragment。
1. 主Fragment的布局(fragment_home_main.xml)
包含TabLayout和ViewPager(如果用新版推荐ViewPager2,后面会补充):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 顶部标签栏 --> <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="fixed" app:tabGravity="fill" /> <!-- 子Fragment容器 --> <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
2. 创建子Fragment
先准备几个子Fragment,比如HomeSubFragment1、HomeSubFragment2,布局可以很简单:
class HomeSubFragment1 : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_home_sub1, container, false) } }
3. 自定义ViewPager适配器
⚠️ 关键:在Fragment内部嵌套子Fragment时,必须用childFragmentManager,不能用fragmentManager,否则子Fragment的生命周期会异常!
class HomeViewPagerAdapter(fragment: Fragment) : FragmentPagerAdapter(fragment.childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { // 标签标题 private val tabTitles = arrayOf("推荐", "热门", "最新") // 子Fragment列表 private val fragmentList = listOf( HomeSubFragment1(), HomeSubFragment2(), HomeSubFragment3() ) override fun getCount(): Int = fragmentList.size override fun getItem(position: Int): Fragment = fragmentList[position] override fun getPageTitle(position: Int): CharSequence? = tabTitles[position] }
4. 主Fragment的逻辑代码
把TabLayout和ViewPager绑定起来:
class HomeMainFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_home_main, container, false) val tabLayout = view.findViewById<TabLayout>(R.id.tab_layout) val viewPager = view.findViewById<ViewPager>(R.id.view_pager) // 设置适配器 val adapter = HomeViewPagerAdapter(this) viewPager.adapter = adapter // 绑定TabLayout和ViewPager tabLayout.setupWithViewPager(viewPager) // 可选:设置预加载页面数,避免频繁创建销毁 viewPager.offscreenPageLimit = 2 return view } }
三、升级方案:用ViewPager2替代ViewPager
ViewPager2是官方推荐的升级版,支持横向/纵向滑动,更稳定。这里快速给出适配代码:
1. 布局替换为ViewPager2
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" />
2. 自定义FragmentStateAdapter
class HomeViewPager2Adapter(fragment: Fragment) : FragmentStateAdapter(fragment) { private val tabTitles = arrayOf("推荐", "热门", "最新") private val fragmentList = listOf( HomeSubFragment1(), HomeSubFragment2(), HomeSubFragment3() ) override fun getItemCount(): Int = fragmentList.size override fun createFragment(position: Int): Fragment = fragmentList[position] // 提供标签标题 fun getTabTitle(position: Int): String = tabTitles[position] }
3. 主Fragment中绑定TabLayout和ViewPager2
需要用TabLayoutMediator来绑定:
class HomeMainFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_home_main, container, false) val tabLayout = view.findViewById<TabLayout>(R.id.tab_layout) val viewPager2 = view.findViewById<ViewPager2>(R.id.view_pager2) val adapter = HomeViewPager2Adapter(this) viewPager2.adapter = adapter // 绑定TabLayout和ViewPager2 TabLayoutMediator(tabLayout, viewPager2) { tab, position -> tab.text = adapter.getTabTitle(position) }.attach() return view } }
四、关键注意事项
- 嵌套Fragment的FragmentManager选择:必须用
childFragmentManager,否则子Fragment会和主Fragment共用同一个管理器,导致生命周期混乱。 - 状态保持:底部导航切换时用
add+隐藏显示的方式,能保留主Fragment的状态(比如ViewPager的滑动位置)。 - 性能优化:设置
offscreenPageLimit预加载页面,减少Fragment的频繁创建销毁。
内容的提问来源于stack exchange,提问作者user11280870

