【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.TabLayoutandroid:id="@+id/tabLayout"android:layout_width="match_parent"android:layout_height="wrap_content"style="@style/TabStyle"/><androidx.viewpager2.widget.ViewPager2android: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"><TextViewandroid: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@Overridepublic Fragment createFragment(int position) {return fragmentList == null ? null : fragmentList.get(position);}@Overridepublic 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;@Overrideprotected 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() {@Overridepublic 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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3268100.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

mysql触发器与存储过程练习

建立两个表:goods(商品表)、orders(订单表) 建立触发器&#xff0c;订单表中增加订单数量后&#xff0c;商品表商品数量同步减少对应的商品订单出数量,并测试 建立触发器&#xff0c;实现功能:客户取消订单&#xff0c;恢复商品表对应商品的数量 建立触发器&#xff0c;实现功…

Florence2:Advancing a unified representation for a variety of vision tasks

Florence-2模型:开启统一视觉基础模型的新篇章_florence -2-CSDN博客文章浏览阅读1.1k次,点赞108次,收藏109次。Florence-2是由微软Azure AI团队开发的一款多功能、统一的视觉模型。它通过统一的提示处理不同的视觉任务,表现出色且优于许多大型模型。Florence-2的设计理念是…

IAR使用调试详解

目录 1 IAR功能介绍 1.1 File文件菜单 1.2 Edit编辑菜单 1.3 View视图菜单 1.4 Projcet工程菜单 1.5Debug调试菜单 1.6 Disassembly反汇编菜单 1.7 Simulator下载调试工具 1.8 Tools工具菜单 1.9 Window窗口菜单 1.10 Help帮助菜单 2 IAR设置 2.1 插入/编辑模板 2…

【优秀python算法毕设】基于python时间序列模型分析气温变化趋势的设计与实现

1 绪论 1.1 研究背景与意义 在气候变化日益受到全球关注的背景下&#xff0c;天气气温的变化已经对人们的生活各方面都产生了影响&#xff0c;人们在外出时大多都会在手机上看看天气如何&#xff0c;根据天气的变化来决定衣物的穿着和出行的安排。[1]如今手机能提供的信息已经…

mysql中You can’t specify target table for update in FROM clause错误

mysql中You can’t specify target table for update in FROM clause错误 You cannot update a table and select directly from the same table in a subquery. mysql官网中有这句话&#xff0c;我们不能在一个语句中先在子查询中从某张表查出一些值&#xff0c;再update这张表…

项目总结:认证授权

文章目录 前言一、认证授权1.用户身份认证2.用户授权 二、业务流程1.统一认证2.单点登录3.第三方认证 二、Spring Security 认证1.Spring Security2.认证授权入门 前言 会议发布后用户通过页面进行查看。如何去记录用户的会议记录呢&#xff1f;要想掌握用户需要参会的情况就需…

【数据结构】栈(基于数组、链表实现 + GIF图解 + 原码)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构 &#x1f4da;本系列文章为个人学…

[OJ]水位线问题,1.采用回溯法(深度优先遍历求解)2.采用广度优先遍历求解

1.深度优先遍历 使用回溯法,深度优先遍历利用栈先进后出的特点,在加水控制水量失败时, 回到最近一次可对水进行加水与否的位置1.对于给定水量k,是否在[l,r]之间&#xff0c; 是:是否加水(加水y,用掉x,是否在[l,r]之间)(不加水y,用掉x,是否在[l,r]之间)先尝试加水&#xff0c;如…

AMQP-核心概念-3

本文参考以下链接摘录翻译&#xff1a; https://www.rabbitmq.com/tutorials/amqp-concepts 队列&#xff08;Queues&#xff09; AMQP 0-9-1模型中的队列和其他消息任务队列系统中的队列非常相似&#xff1a;它们用于存储被应用消费的消息。队列和交换机有一些相同的属性&…

js 习题 3

文章目录 绪论12345678910 求最长公共后缀111213 最大公约数1415结语 绪论 『虽有遗憾&#xff0c;绝不后悔。』—— 「古剑奇谭」 1 let buf"";process.stdin.on("readable",function(){let chunkprocess.stdin.read();if(chunk){bufchunk.toString();} …

Linux下git入门操作

0.创建仓库 可以按这个配置来&#xff0c;.gitignore中存放了上传时忽略的文件类型后缀。 1.clone仓库 在gitee上创建好仓库&#xff0c;点击克隆/下载&#xff0c; 复制地址fyehong/Linux_notes 。 在所需的文件夹中放置仓库。比如我在文件夹lesson9下存储仓库。就在less…

MySQL练手 --- 1141. 查询近30天活跃用户数

题目链接&#xff1a;1141. 查询近30天活跃用户数 思路&#xff1a; 题目要求&#xff1a;统计截至 2019-07-27&#xff08;包含2019-07-27&#xff09;&#xff0c;近 30 天的每日活跃用户数&#xff08;当天只要有一条活动记录&#xff0c;即为活跃用户&#xff09; 要计算…

AbutionGraph时序(流式)图数据库开发文档地址

AbutionGraph-时序(流式)图数据库&#xff0c;官方开发文档(API)地址&#xff1a; http://www.thutmose.cn

Java链接elasticsearch8.14.1

项目需求&#xff0c;需要实现海量数据的聚合、查询。因为职业生涯开发使用springboot微服务架构、Java开发的方式&#xff0c;所以&#xff0c;项目前期准备了elasticsearch、kibana、logstash的集群环境&#xff0c;作为服务端&#xff0c;用于数据的收集、存储&#xff1b;但…

『 Linux 』信号的写入与保存

文章目录 信号的发送信号的保存sigset_t 类型与信号集操作函数阻塞信号集(信号屏蔽字)操作函数未决信号集操作函数验证阻塞信号集与未决信号集 信号的发送 $ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10)…

护眼台灯哪个牌子最好?五款平价护眼台灯推荐

如今在快节奏的生活里&#xff0c;不管是上班族还是作为一名学生党&#xff0c;都离不开长时间用眼的情况。高强度的用眼自然会对眼睛伤害大。近视加重可能都还算是小问题&#xff0c;严重的话会出现严重的眼部健康问题&#xff0c;所以&#xff0c;拥有一款合适的护眼台灯是很…

【C++】——红黑树(手撕红黑树,彻底弄懂红黑树)

目录 前言 一 红黑树简介 二 为什么需要红黑树 三 红黑树的特性 四 红黑树的操作 4.1 变色操作 4.2 旋转操作 4.3 插入操作 4.4 红黑树插入代码实现 4.5 红黑树的删除 五 红黑树迭代器实现 总结 前言 我们之前都学过ALV树&#xff0c;AVL树的本质就是一颗平…

【YOLOv5/v7改进系列】引入中心化特征金字塔的EVC模块

一、导言 现有的特征金字塔方法过于关注层间特征交互而忽视了层内特征的调控。尽管有些方法尝试通过注意力机制或视觉变换器来学习紧凑的层内特征表示&#xff0c;但这些方法往往忽略了对密集预测任务非常重要的被忽视的角落区域。 为了解决这个问题&#xff0c;作者提出了CF…

力扣高频SQL 50题(基础版)第十七题

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第十七题1075. 项目员工 I题目说明思路分析实现过程准备数据实现方式结果截图 力扣高频SQL 50题&#xff08;基础版&#xff09;第十七题 1075. 项目员工 I 题目说明 项目表 Project&#xff1a; ----------------…

uniapp引入自定义图标

目录 一、选择图标&#xff0c;加入购物车 二、下载到本地 三、导入项目 四、修改字体引用路径 五、开始使用 这里以扩展iconfont图标为例 官网&#xff1a;iconfont-阿里巴巴矢量图标库 一、选择图标&#xff0c;加入购物车 二、下载到本地 直接点击下载素材&#xff0…