【Android】使用ViewPager2与TabLayout实现顶部导航栏+页面切换

【Android】使用ViewPager2与TabLayout实现顶部导航栏+页面切换

TabLayout与ViewPager2概述

TabLayout

image-20240726195519885

TabLayout 是 Android 支持库中的一个组件,它是 Design 支持库的一部分。TabLayout 提供了一个水平的标签页界面,允许用户在不同的视图或数据集之间进行切换。以下是 TabLayout 的一些主要特性和使用方法

主要特性

  1. 标签页模式TabLayout 可以显示多个标签页,每个标签页可以包含文本、图标或两者兼有。
  2. 动态标签页:可以动态地添加、删除或重新排序标签页。
  3. ViewPager 结合使用TabLayout 可以与 ViewPager 无缝集成,使得用户在滑动 ViewPager 的页面时,TabLayout 的选中标签页也会相应地变化。
  4. 自定义标签页:可以自定义标签页的布局、样式和行为。
  5. 滚动标签页:当标签页数量超过屏幕宽度时,它们可以水平滚动。

官方文档:

TabLayout | Android Developers (google.cn)

ViewPager2

ViewPager2 是 Android Jetpack 库中的一个组件,它是 ViewPager 的一个改进版本,提供了更好的性能和更多的功能。ViewPager2 允许用户左右滑动来浏览不同的视图,类似于一个滑动页面的控件。

主要特性

  1. 更好的性能ViewPager2 优化了滑动性能,特别是在处理大量页面或复杂视图时。
  2. 垂直和水平滑动:支持水平和垂直滑动,而 ViewPager 仅支持水平滑动。
  3. 动态添加和删除页面:可以动态地添加、删除或重新排序页面。
  4. TabLayout 集成:可以与 TabLayout 集成,实现标签页和页面视图的联动。
  5. 自定义适配器:支持自定义适配器,可以灵活地控制页面内容的显示。
  6. 滑动事件监听:提供滑动事件的监听,可以获取用户滑动的详细信息。

官方文档:

ViewPager2 | Jetpack | Android Developers (google.cn)

使用 ViewPager2 创建包含标签的滑动视图 | Android Developers (google.cn)

示例展示

6b75dd083172f70a8732 -small-original

本篇博客,笔者将带大家实现这样一个标题栏+Fragment水平切换页面的案例。

步骤详解

创建Activity并加入TabLayout和ViewPager2组件

创建Activity的步骤不再赘述,这里放上写好的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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    style="@style/LayoutStyle">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/TabStyle"/>


    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" />

</LinearLayout>

顺便展示一下Tab的样式配置:

    <style name="TabStyle">
        
        <item name="tabGravity">fill</item>
        <item name="tabIndicatorFullWidth">false</item>
        <item name="tabIndicatorAnimationMode">elastic</item>
        <item name="tabMode">fixed</item>
        <item name="tabUnboundedRipple">false</item>
        <item name="tabBackground">@color/white</item>
        <item name="tabTextAppearance">@style/TabTextStyle</item>
        <item name="tabIndicator">@drawable/shape_tab_indicator</item>
        <item name="tabIndicatorColor">@color/text_change_color</item>
        <item name="tabTextColor">@color/black</item>
        <item name="tabSelectedTextColor">@color/text_change_color</item>
    </style>
  • tabGravity: 这个属性设置Tab的对齐方式,fill表示Tab将填充整个TabLayout的宽度。
  • tabIndicatorFullWidth: 设置指示器是否占据整个Tab的宽度,false表示指示器不会占据整个Tab的宽度。
  • tabIndicatorAnimationMode: 指示器动画模式,elastic表示弹性动画效果。
  • tabMode: 设置Tab的模式,fixed表示Tab的数量是固定的。
  • tabUnboundedRipple: 是否允许未绑定的涟漪效果,false表示不允许。
  • tabBackground: 设置Tab的背景颜色,这里使用了颜色资源@color/white
  • tabTextAppearance: 设置Tab文本的样式,这里引用了一个样式资源@style/TabTextStyle
  • tabIndicator: 设置Tab指示器的形状,这里引用了一个可绘制资源@drawable/shape_tab_indicator
  • tabIndicatorColor: 设置Tab指示器的颜色,使用了颜色资源@color/text_change_color
  • tabTextColor: 设置Tab未选中时的文本颜色,使用了颜色资源@color/black
  • tabSelectedTextColor: 设置Tab选中时的文本颜色,同样使用了颜色资源@color/text_change_color

仅作样式展示,大家喜欢的话可以直接cv走,如果想要自定义可以自行去网络上搜索配置属性,或者是查看源码。

创建需要的Fragments

下一步,我们创建需要的Fragments,这里仅演示一下标题栏的写法,故而Fragment就简单一些。

简简单单,一个Fragment。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TextFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="人尔 女子"
        android:textSize="40dp"/>

</LinearLayout>

大概长这样:

image-20240726201253079

创建FragmentAdapter(关联Fragment和ViewPager2)

接下来我们来写FragmentAdapter。

首先创建一个类,使他继承FragmentStateAdapter

FragmentStateAdapter 是 Android 开发中的一部分,它是一个适配器类,用于管理与 Fragment 相关的数据集合。FragmentStateAdapter 继承自 FragmentActivity.CallbacksFragmentManager.FragmentLifecycleCallbacks,因此它可以接收到 Fragment 生命周期的回调,并根据数据的变化来管理 Fragment 的状态。

FragmentStateAdapter 通常与 ViewPager2 一起使用,为 ViewPager2 提供 Fragment。

image-20240726201541472

刚继承完就爆红了,不要慌,alt+enter重写几个方法。

FragmentStateAdapter要求我们重写createFragment方法和getItemCount方法,

  1. createFragment(int position):
    • 这个方法用于创建并返回一个 Fragment 对象,对应于 ViewPager2 中的每个页面。
    • position 参数表示当前页面的位置(索引),从0开始。
    • 你需要重写这个方法来提供具体的 Fragment 实例。
  2. getItemCount():
    • 这个方法返回 ViewPager2 中页面的数量。
    • 你需要重写这个方法来指定你的 ViewPager2 应该有多少个页面。

先创建一个管理fragment的list成员变量,用于管理fragment页面,然后填充几个函数即可,别忘了写一个新的构造方法。

代码如下:

public class FragmentAdapter extends FragmentStateAdapter {
    private List<Fragment> fragmentList;
    public FragmentAdapter(@NonNull FragmentActivity fragmentActivity
            , List<Fragment> fragmentList) {
        super(fragmentActivity);
        this.fragmentList = fragmentList;
    }

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

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

为了防止空指针异常,我们在每次返回时,判断一次fragment是否为空。

将ViewPager2配置好

配置ViewPager2的步骤也不复杂,首先回到MainActivity中,把刚刚写好的适配器和用于管理的List列表写出来:

private FragmentAdapter fragmentAdapter;
private List<Fragment> fragmentList;

然后将适配器实例化,再设置给viewPager组件即可,完整代码如下:

public class MainActivity extends AppCompatActivity {

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

        initData();
        fragmentAdapter = new FragmentAdapter(this,fragmentList);
        binding.viewPager.setAdapter(fragmentAdapter);
    }

    private void initData() {
        fragmentList = new ArrayList<>();
        fragmentList.add(new TextFragment());
        fragmentList.add(new TextFragment());
        fragmentList.add(new TextFragment());
    }
}

省去实例化Fragment的过程,仅仅两行代码就完成配置了,真是便便又捷捷呀。

我们不妨点进去这个ViewPager2的setAdapter方法,看看他都干了些什么。在网上搜索ViewPager2和ViewPager的区别的时候,总能看到一句话:两者最大的区别就是可以直接把ViewPager2看成RecyclerView,先放下这些疑惑,我们点进去看看:

image-20240726203454838

点进去第一眼就是这个非常显眼的RecyclerView,真是封封又装装啊。

来都来了,我们看看这个方法都做了些什么。

    public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
        final Adapter<?> currentAdapter = mRecyclerView.getAdapter();
        mAccessibilityProvider.onDetachAdapter(currentAdapter);
        unregisterCurrentItemDataSetTracker(currentAdapter);
        mRecyclerView.setAdapter(adapter);
        mCurrentItem = 0;
        restorePendingState();
        mAccessibilityProvider.onAttachAdapter(adapter);
        registerCurrentItemDataSetTracker(adapter);
    }

笔者大概查询了一下,这段代码首先用

final Adapter<?> currentAdapter = mRecyclerView.getAdapter();

这段代码获取当前RecyclerView的适配器,并将其存储在currentAdapter变量中。

mAccessibilityProvider.onDetachAdapter(currentAdapter);

unregisterCurrentItemDataSetTracker(currentAdapter);

这段代码通知辅助功能提供者(Accessibility Provider)当前适配器已经被分离(detached)。这是为了在适配器更换时更新辅助功能的状态,而后取消注册当前适配器的数据集变化跟踪器。这通常是在适配器更换时进行的,以确保旧的适配器不再被跟踪。

mRecyclerView.setAdapter(adapter);

这行代码将一个新的适配器adapter设置给RecyclerView。这是实际更换适配器的操作。

restorePendingState();

这行代码尝试恢复挂起的状态。这可能是一个自定义方法,用于在适配器更换后恢复之前的状态,例如恢复滚动位置等。

mAccessibilityProvider.onAttachAdapter(adapter);

这行代码通知辅助功能提供者新的适配器已经被附加(attached)。这是为了确保辅助功能能够正确地与新的适配器交互。

registerCurrentItemDataSetTracker(adapter);

这行代码注册新的适配器的数据集变化跟踪器。这允许RecyclerView监听数据集的变化,并在变化发生时进行适当的更新。

大概总结一下,这段代码的作用就是更加安全地为内置的RecyclerView更换了适配器,并且确保一系列的辅助功能能正确的运行与更新。

看都看了,不妨再看一点:

image-20240726205134808

翻着翻着发现ViewPager2的构造方法都要用到一个initalize方法,我们看看这个initalize方法都干了什么:

image-20240726205308500

非常长一大串,不过没关系,我们忽略其中的大部分内容。看到这句话:

image-20240726205419932

这两段想必并不陌生,这是创建RecyclerView视图时必不可缺的两句话,第一句用于创建Recycler实例,第二句用于创建一个线性布局管理器,而后将管理器设置到RecyclerView。

image-20240726205556239

最后的这句代码也能望文生义,也就是将刚刚创建的RecyclerView附加到父视图上。

以上就是对ViewPager2的简单介绍了,希望对大家理解这个组件能有一些帮助。

关联TabLayout

在Activity的onCreate方法中增加一句话即可:

new TabLayoutMediator(binding.tabLayout, binding.viewPager, new TabLayoutMediator.TabConfigurationStrategy() {
    @Override
    public void onConfigureTab(@NonNull TabLayout.Tab tab, int i) {
        tab.setText("nihao");
    }
}).attach();

这段代码是Android开发中用于连接TabLayoutViewPager2(或ViewPager)的TabLayoutMediator类的使用示例。TabLayoutMediator是一个实用工具类,它帮助开发者将TabLayout的标签与ViewPagerViewPager2的页面同步。以下是代码的详细分析:

创建TabLayoutMediator实例:

new TabLayoutMediator(binding.tabLayout, binding.viewPager, …)

这行代码创建了一个新的TabLayoutMediator对象。它接收三个参数:

  • binding.tabLayout: 一个TabLayout实例,表示包含标签的组件。
  • binding.viewPager: 一个ViewPagerViewPager2实例,表示用户可以左右滑动浏览的组件。
  • 一个实现了TabLayoutMediator.TabConfigurationStrategy接口的匿名类实例,这个接口定义了如何配置每个标签。

配置标签:

new TabLayoutMediator.TabConfigurationStrategy() {…}

这是一个匿名内部类,实现了TabLayoutMediator.TabConfigurationStrategy接口。这个接口包含一个方法onConfigureTab,用于配置每个标签。

@Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int i) { … }

这是onConfigureTab方法的重写,它接收两个参数:

  • @NonNull TabLayout.Tab tab: 当前需要配置的Tab对象。
  • int i: 表示当前标签在TabLayout中的位置(索引)。

连接TabLayoutViewPager:

.attach();

  • 这行代码调用TabLayoutMediator实例的attach方法,它的作用是将TabLayoutViewPager连接起来。一旦连接,当ViewPager的页面发生变化时,TabLayout会相应地更新当前选中的标签。同样,当用户点击某个标签时,ViewPager会滚动到对应的页面。

笔者不小心手滑,刚写完代码就点进去了一个陌生的页面。

image-20240726210908910

原来是attach()方法的实现原理,正巧笔者也对TabLayout如何连接两个组件非常好奇,不妨来看一看。

image-20240726211059830

首先这个方法会检查Mediator是否连接到了viewPager,而后会检查viewPager的适配器是否存在,保障其安全性。

一切准备就绪后,将attached的状态设置为true,接下来就是激动人心的逻辑环节了:

image-20240726211240514

创建页面变化回调:

  • this.onPageChangeCallback = new TabLayoutOnPageChangeCallback(this.tabLayout);
    
    • 创建一个 TabLayoutOnPageChangeCallback 实例,用于处理 ViewPager2 页面变化事件,并更新 TabLayout

注册页面变化回调:

  • this.viewPager.registerOnPageChangeCallback(this.onPageChangeCallback);
    
    • 将创建的页面变化回调注册到 ViewPager2

创建标签选择监听器:

  • this.onTabSelectedListener = new ViewPagerOnTabSelectedListener(this.viewPager, this.smoothScroll);
    

    创建一个 ViewPagerOnTabSelectedListener 实例,用于处理 TabLayout 标签选择事件,并更新 ViewPager2

添加标签选择监听器:

  • this.tabLayout.addOnTabSelectedListener(this.onTabSelectedListener);
    

    将创建的标签选择监听器添加到 TabLayout

设置标签滚动位置:

  • this.tabLayout.setScrollPosition(this.viewPager.getCurrentItem(), 0.0F, true);
  • 设置 TabLayout 中当前选中标签的滚动位置,确保用户可以看到当前页面对应的标签。

中间有一段实在不知道干什么用的,就留给读者去解决啦~

结语

本文参考:

【Android】ViewPager2和TabLayout协同使用,实现多Fragment页面切换类似于QQ音乐,bilibili效果_tablayout和viewpager2-CSDN博客

【Android】ViewPager2监听页面切换事件_viewpager2 监听-CSDN博客

安卓:TabLayout+ViewPager2+Fragment使用(java)_tablayout viewpager2 fragment-CSDN博客

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 ViewPager2+TabLayout+Fragment 实现页面切换,需要以下步骤: 1. 在 XML 布局文件中定义 ViewPager2TabLayout,并将它们嵌套在一个父布局中。 ```xml <androidx.constraintlayout.widget.ConstraintLayout 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" app:tabMode="scrollable" app:tabGravity="center"/> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/tab_layout" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> ``` 2. 创建 Fragment,并实现 ViewPager2 的适配器。 ```kotlin class MyFragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { private val fragmentList = listOf( FirstFragment(), SecondFragment(), ThirdFragment() ) override fun getItemCount() = fragmentList.size override fun createFragment(position: Int) = fragmentList[position] } ``` 3. 在 Activity 或 Fragment 中,初始化 ViewPager2TabLayout,并将适配器设置给 ViewPager2。 ```kotlin val viewPager: ViewPager2 = findViewById(R.id.view_pager) val tabLayout: TabLayout = findViewById(R.id.tab_layout) val adapter = MyFragmentAdapter(this) viewPager.adapter = adapter TabLayoutMediator(tabLayout, viewPager) { tab, position -> tab.text = "Tab ${position + 1}" }.attach() ``` 这样就可以实现 ViewPager2+TabLayout+Fragment 实现页面切换了。注意,TabLayoutMediator 是用来关联 TabLayout 和 ViewPager2 的,它的第一个参数是 TabLayout,第二个参数是 ViewPager2,第三个参数是一个回调函数,用来设置 TabLayout 的标签文本。在最后一行调用 attach() 方法即可完成关联。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值