【Android】用 ViewPager2 + Fragment + TabLayout 实现标签页切换
【Android】用 ViewPager2 + Fragment + TabLayout 实现标签页切换
一、引入:什么是 ViewPager2 ?
在我们日常使用的应用中,常常会遇到可以左右滑动切换内容的界面,比如知乎首页的推荐/热榜/关注标签页、微博的频道分类、相册中的多图浏览,甚至首次打开 App 时的欢迎引导页。这类界面背后的滑动切换机制,通常就是通过 Android 提供的 ViewPager 或其升级版 ViewPager2 实现的。ViewPager2 是 Android 提供的现代化分页滑动组件,支持横向与纵向滑动,具备高效的页面管理能力,是实现多页切换、Tab 联动与内容滑动浏览等功能的首选方案。
注:ViewPager2 已全面替代 ViewPager,是官方推荐的新项目默认使用的分页滑动组件。
概述:ViewPager2 是基于 RecyclerView 实现的页面切换控件,继承了其高效的视图复用机制和灵活布局能力,支持水平与垂直滑动,通过设置 offscreenPageLimit
控制缓存策略,提供更高性能和更低内存开销。使用 FragmentStateAdapter 或 RecyclerView.Adapter 作为适配器,适配灵活,支持页面生命周期自动管理,同时还支持 PageTransformer 实现炫酷的切换动画,是替代旧版 ViewPager 的推荐方案。
二、ViewPager2 的基础使用
使用前先添加依赖 (build.gradle):
dependencies {// ...implementation 'androidx.viewpager2:viewpager2:1.1.0'implementation 'com.google.android.material:material:1.12.0'
}
注: 检查并使用最新稳定版本号(ViewPager2|Google 的 Maven 存储库)
1. 在布局文件 (activity_main.xml)中添加 ViewPager2
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
2. 制作一个 Fragment
2.1 创建一个布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#19CFBE"><TextViewandroid:id="@+id/textview"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="泥嚎"android:layout_gravity="center"android:textSize="45sp"/></FrameLayout>
2.2 创建一个 Fragment 类绑定这个布局
public class HomeFragment extends Fragment {private FragmentBinding binding;@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {binding = FragmentBinding.inflate(inflater, container, false);return binding.getRoot();}public void onDestroyView() {super.onDestroyView();binding = null;}
}
3. 创建适配器 (MyAdapter.java)
public class MyAdapter extends FragmentStateAdapter {private final List<Fragment> fragmentList;public MyAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragmentList) {super(fragmentActivity);this.fragmentList = fragmentList;}@NonNull@Overridepublic Fragment createFragment(int position) {// 用于返回指定的fragment界面return fragmentList.get(position);}@Overridepublic int getItemCount() {// 用于加载容器大小return fragmentList != null ? fragmentList.size() : 0;}
}
自定义 Adapter 并继承 FragmentStateAdapter
ViewPager2 的两种适配器类型:
RecyclerView.Adapter
- 这是一个基础类,需要你自己创建 ViewHolder、绑定 View。
- 适合每个页面是简单的布局 View,不需要 Fragment。
- 可以直接继承
RecyclerView.Adapter<MyViewHolder>
来实现。- 优点:轻量,适合纯视图页面,不用管理 Fragment 复杂生命周期。
- 缺点:没有 Fragment 的独立生命周期,不能使用 Fragment 特性。
FragmentStateAdapter
- 继承自
RecyclerView.Adapter
,但专门为 ViewPager2 管理 Fragment 做了封装。- 内部会自动帮你管理 Fragment 的添加、显示、销毁和状态保存。
- 适合每页是 Fragment 的场景,比如每页一个独立的界面。
- 需要实现
createFragment(int position)
来返回对应位置的 Fragment 实例。
4. 在活动中使用
List<Fragment> fragmentList = new ArrayList<>();fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());fragmentList.add(new HomeFragment());// 设置适配器MyAdapter adapter = new MyAdapter(this, fragmentList);binding.viewPager.setAdapter(adapter);
三、ViewPager2 + Fragment + TabLayout
TabLayout
是 Android 中用于实现选项卡导航的控件,常与 ViewPager2
搭配使用,它可以在界面顶部展示一排可切换的标签页,每个标签通常对应一个页面内容(如 Fragment),用户点击或滑动即可在各页面之间切换。
1. 在布局文件 (activity_main.xml)中添加 ViewPager2 和 TabLayout
<?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:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><com.google.android.material.tabs.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/transparent"android:elevation="0dp"app:tabMode="scrollable"app:tabIndicatorColor="@color/pink"/><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/></LinearLayout>
XML 属性 | 方法 | 取值 | 说明 |
---|---|---|---|
app:tabMode | setTabMode() | fixed scrollable | 标签显示模式:固定宽度/可滚动 |
app:tabGravity | setTabGravity() | center fill | 标签对齐方式:居中/填充 |
app:tabContentStart | setPadding() | 尺寸值 (如 16dp ) | 第一个标签的起始边距 |
app:tabInlineLabel | setInlineLabel() | true false | 图标与文本是否同行显示 |
app:tabIndicatorColor | setSelectedTabIndicatorColor() | 颜色值 | 指示器颜色 |
app:tabIndicatorHeight | setSelectedTabIndicatorHeight() | 尺寸值 | 指示器高度 |
app:tabIndicatorFullWidth | setTabIndicatorFullWidth() | true false | 指示器是否与标签等宽 |
app:tabIndicator | setSelectedTabIndicator() | Drawable 资源 | 自定义指示器 Drawable |
app:tabIndicatorGravity | setTabIndicatorGravity() | bottom top center stretch | 指示器位置:底部/顶部/居中/拉伸 |
app:tabIndicatorAnimationDuration | 无 | 毫秒值 (如 300 ) | 指示器动画时长 |
app:tabIndicatorAnimationMode | 无 | linear elastic | 指示器动画模式:线性/弹性 |
app:tabTextAppearance | setTabTextAppearance() | 样式资源 (如 @style/MyTab ) | 文本外观样式 |
app:tabTextColor | setTabTextColors() | ColorStateList | 文本颜色(支持选择器) |
app:tabIconTint | setTabIconTint() | ColorStateList | 图标着色(支持选择器) |
app:tabBackground | setTabBackground() | Drawable 资源 | 标签背景(支持选择器) |
app:tabRippleColor | setTabRippleColor() | ColorStateList | 点击波纹效果颜色 |
app:tabMinWidth | setTabMinWidth() | 尺寸值 | 标签最小宽度 |
app:tabMaxWidth | setTabMaxWidth() | 尺寸值 | 标签最大宽度 |
app:tabPaddingStart | setTabPadding() | 尺寸值 | 标签起始内边距 |
2. 使用 TabLayoutMediator 将两者关联
new TabLayoutMediator(binding.tabLayout, binding.viewPager,new TabLayoutMediator.TabConfigurationStrategy() {@Overridepublic void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {//自定义TabViewTextView tabView = new TextView(MainActivity.this);tabView.setText("Fragment " + position);//将tabbView绑定到tabtab.setCustomView(tabView);}}).attach();
创建一个 TabLayoutMediator
实例,传入三个参数:
binding.tabLayout
:要联动的 TabLayout。binding.viewPager
:要联动的 ViewPager2。TabConfigurationStrategy
:回调接口,用于配置每一个 Tab(比如设置标题、图标等)。
onConfigureTab(...)
每个 Tab 初始化时,这个方法都会被调用一次,对应 ViewPager2 的每一页。
tab.setCustomView(tabView);
设置 Tab 的视图,可以完全控制 Tab 的外观,而不再受默认样式限制。也可以用自己写的 layout 文件,比如:
View customTab = LayoutInflater.from(context).inflate(R.layout.my_tab_layout, null); tab.setCustomView(customTab);
3. ViewPager2 的页面切换监听
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {@Overridepublic void onPageSelected(int position) {super.onPageSelected(position);// 这里可以处理页面切换时的逻辑}});
这是给 ViewPager2
注册一个 页面变更回调监听器(PageChangeCallback),当用户手动或编程切换页面时,可以在对应的方法中执行你想要的逻辑。
registerOnPageChangeCallback(...)
向 ViewPager2 注册一个页面变化的监听器,类型是
ViewPager2.OnPageChangeCallback
,可以监听 当前页面的状态变化(比如选中、滑动、滚动状态变化等)
new ViewPager2.OnPageChangeCallback()
这是创建一个监听器对象,并重写其回调方法。该监听器包含以下常用方法:
onPageSelected(int position)
- 调用时机:页面切换完成后(即滑动结束,页面完全展示出来时)。
- 适合场景:
- 更新 TabLayout 高亮
- 改变底部按钮状态
- 动态加载数据(懒加载)
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
- 当页面正在滑动中不断回调。
- 可用于页面间动画联动、Tab 动画跟随效果等。
onPageScrollStateChanged(int state)
- 页面滑动状态变化时回调。
state
可能的值:
SCROLL_STATE_IDLE
(0): 空闲状态,页面滑动停止。SCROLL_STATE_DRAGGING
(1): 用户正在滑动。SCROLL_STATE_SETTLING
(2): 页面正在自动滑动到最终位置。
4. 动态添加标签
TabLayout tabLayout = findViewById(binding.tabLayout);// 添加文本标签
tabLayout.addTab(tabLayout.newTab().setText("首页"));// 添加图标标签
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_home));// 添加自定义视图标签
TabLayout.Tab tab = tabLayout.newTab();
tab.setCustomView(R.layout.custom_tab_view);
tabLayout.addTab(tab);
类型 | 方法 | 适用场景 |
---|---|---|
文本标签 | setText("标题") | 简洁的标题导航 |
图标标签 | setIcon(R.drawable.xxx) | 图标导航栏(常见于底部) |
自定义标签 | setCustomView(R.layout.xxx) | 图文结合、动画、徽章等复杂样式 |
5. 标签选择监听
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {@Overridepublic void onTabSelected(TabLayout.Tab tab) {// 标签被选中时触发int position = tab.getPosition();viewPager2.setCurrentItem(position);}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {// 标签取消选中时触发}@Overridepublic void onTabReselected(TabLayout.Tab tab) {// 已选中标签再次点击时触发}
});
6. 禁用 ViewPager2 左右滑动翻页
//false表示禁止,true表示允许
binding.viewPager.setUserInputEnabled(false);
ViewPager2
内部是通过 RecyclerView
实现滑动的。setUserInputEnabled(false)
会禁用一切手势滑动,但不会影响通过 setCurrentItem()
代码设置页面切换。