【Android】用 ViewPager2 + Fragment + TabLayout 实现标签页切换

文章目录

  • 【Android】用 ViewPager2 + Fragment + TabLayout 实现标签页切换
    • 一、引入:什么是 ViewPager2 ?
    • 二、ViewPager2 的基础使用
      • 1. 在布局文件 (activity_main.xml)中添加 ViewPager2
      • 2. 制作一个 Fragment
        • 2.1 创建一个布局文件
        • 2.2 创建一个 Fragment 类绑定这个布局
      • 3. 创建适配器 (MyAdapter.java)
      • 4. 在活动中使用
    • 三、ViewPager2 + Fragment + TabLayout
      • 1. 在布局文件 (activity_main.xml)中添加 ViewPager2 和 TabLayout
      • 2. 使用 TabLayoutMediator 将两者关联
      • 3. ViewPager2 的页面切换监听
      • 4. 动态添加标签
      • 5. 标签选择监听
      • 6. 禁用 ViewPager2 左右滑动翻页
      • 7. 效果展示

【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


<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.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

LinearLayout>

2. 制作一个 Fragment

2.1 创建一个布局文件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#19CFBE">

    <TextView
        android: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
    @Override
    public 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
    @Override
    public Fragment createFragment(int position) {
        // 用于返回指定的fragment界面
        return fragmentList.get(position);
    }

    @Override
    public int getItemCount() {
        // 用于加载容器大小
        return fragmentList != null ? fragmentList.size() : 0;
    }
}

自定义 Adapter 并继承 FragmentStateAdapter

ViewPager2 的两种适配器类型

RecyclerView.Adapter

  • 这是一个基础类,需要你自己创建 ViewHolder、绑定 View。
  • 适合每个页面是简单的布局 View,不需要 Fragment。
  • 可以直接继承 RecyclerView.Adapter 来实现。
  • 优点:轻量,适合纯视图页面,不用管理 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);

效果如下:
【Android】用 ViewPager2 + Fragment + TabLayout 实现标签页切换_第1张图片

三、ViewPager2 + Fragment + TabLayout

TabLayout 是 Android 中用于实现选项卡导航的控件,常与 ViewPager2 搭配使用,它可以在界面顶部展示一排可切换的标签页,每个标签通常对应一个页面内容(如 Fragment),用户点击或滑动即可在各页面之间切换。

1. 在布局文件 (activity_main.xml)中添加 ViewPager2 和 TabLayout


<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.TabLayout
        android: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.ViewPager2
        android: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() {
                    @Override
                    public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                        //自定义TabView
                        TextView tabView = new TextView(MainActivity.this);

                        tabView.setText("Fragment " + position);

                        //将tabbView绑定到tab
                        tab.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() {
            @Override
            public 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() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        // 标签被选中时触发
        int position = tab.getPosition();
        viewPager2.setCurrentItem(position);
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
        // 标签取消选中时触发
    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
        // 已选中标签再次点击时触发
    }
});

6. 禁用 ViewPager2 左右滑动翻页

//false表示禁止,true表示允许
binding.viewPager.setUserInputEnabled(false);

ViewPager2 内部是通过 RecyclerView 实现滑动的。setUserInputEnabled(false) 会禁用一切手势滑动,但不会影响通过 setCurrentItem() 代码设置页面切换。

7. 效果展示

代码如下:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        EdgeToEdge.enable(this);
        ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        List<Fragment> fragmentList = List.of(
                new LiveStreamingFragment(),
                new RecommendFragment(),
                new HotFragment(),
                new AnimationFragment(),
                new FilmFragment(),
                new RedFragment()
        );

        MyAdapter adapter = new MyAdapter(this, fragmentList);
        binding.viewPager.setAdapter(adapter);
        
        new TabLayoutMediator(binding.tabLayout, binding.viewPager,
                new TabLayoutMediator.TabConfigurationStrategy() {
                    @Override
                    public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                        switch (position) {
                            case 0:
                                tab.setText("直播");
                                break;
                            case 1:
                                tab.setText("推荐");
                                break;
                            case 2:
                                tab.setText("热门");
                                break;
                            case 3:
                                tab.setText("动画");
                                break;
                            case 4:
                                tab.setText("影视");
                                break;
                            case 5:
                                tab.setText("新征程");
                                break;
                            default:
                                tab.setText("未知");
                                break;
                        }
                    }
                }).attach();

        binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);

            }
        });

    }
}

public class MyAdapter extends FragmentStateAdapter {
    private final List<Fragment> fragmentList;

    public MyAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragmentList) {
        super(fragmentActivity);
        this.fragmentList = fragmentList;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragmentList.get(position);
    }

    @Override
    public int getItemCount() {
        return fragmentList != null ? fragmentList.size() : 0;
    }
}

public class LiveStreamingFragment extends Fragment {
    private FragmentLiveStreamingBinding binding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = FragmentLiveStreamingBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        binding = null;
    }
}

public class RecommendFragment extends Fragment {
    private FragmentRecommendBinding binding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = FragmentRecommendBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        binding = null;
    }
}

// 其余几个碎片和上面的类似
// ...

activity_main.xml 代码如下:

<?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.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent"
        android:elevation="0dp"
        app:tabMode="auto"
        app:tabSelectedTextColor="@color/pink"
        app:tabIndicatorColor="@color/pink"
        app:tabIndicatorGravity="bottom"/>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:src="@drawable/p0"/>

</LinearLayout>

每个碎片布局都是简单的用了一个 ImageView 用来展示图片:

<?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">

    <ImageView
        android:id="@+id/image6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/p12"/>

</FrameLayout>

你可能感兴趣的:(android,学习,java,笔记)