当前位置: 首页 > article >正文

(Kotlin)Android 高效底部导航方案:基于预定义 Menu 和 ViewPager2 的 Fragment 动态绑定实现

支持预定义 Menu 并绑定 Fragment,同时保留动态添加 Tab 的能力
BottomTabHelper.kt

package smartconnection.com.smartconnect.home.utilimport android.content.Context
import android.util.SparseArray
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomnavigation.BottomNavigationView/*** 增强版底部导航助手* 功能:* 1. 支持预定义 Menu 绑定 Fragment* 2. 保留动态添加/移除 Tab 能力* 3. 完善的 Fragment 生命周期管理* 4. 内置平滑过渡动画*/
class BottomTabHelper private constructor(private val activity: FragmentActivity,@IdRes private val viewPagerId: Int,@IdRes private val bottomNavigationViewId: Int
) {private val viewPager: ViewPager2 by lazy { activity.findViewById(viewPagerId) }private val bottomNav: BottomNavigationView by lazy { activity.findViewById(bottomNavigationViewId) }private val fragmentCache = SparseArray<Fragment>()private var currentPosition = 0private var isSmoothScrollEnabled = true// Tab 配置数据类data class TabConfig(@IdRes val menuItemId: Int,val fragment: Fragment,val title: String? = null,val iconResId: Int? = null)init {initViewPager()initBottomNav()}private fun initViewPager() {viewPager.adapter = TabPagerAdapter(activity)viewPager.offscreenPageLimit = 1viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {override fun onPageSelected(position: Int) {super.onPageSelected(position)currentPosition = positionbottomNav.menu.getItem(position).isChecked = truetriggerLazyLoad(position)}})}private fun initBottomNav() {bottomNav.setOnNavigationItemSelectedListener { item ->val position = getPositionForMenuItem(item.itemId)if (position != INVALID_POSITION) {viewPager.setCurrentItem(position, isSmoothScrollEnabled)true} else {false}}}/*** 绑定预定义 Menu 项到 Fragment*/fun bindPredefinedTab(@IdRes menuItemId: Int, fragment: Fragment) {val position = getPositionForMenuItem(menuItemId)if (position != INVALID_POSITION) {fragmentCache.put(position, fragment)viewPager.adapter?.notifyItemChanged(position)// 如果是第一个绑定的 Tab,自动选中if (fragmentCache.size() == 1) {viewPager.setCurrentItem(0, false)}}}/*** 动态添加 Tab (可选)*/fun addTab(config: TabConfig) {val newPosition = fragmentCache.size()// 添加到底部导航bottomNav.menu.add(0, config.menuItemId, newPosition, config.title ?: "").apply {config.iconResId?.let { setIcon(it) }}// 缓存 FragmentfragmentCache.put(newPosition, config.fragment)viewPager.adapter?.notifyItemInserted(newPosition)}private fun getPositionForMenuItem(menuItemId: Int): Int {val menu = bottomNav.menufor (i in 0 until menu.size()) {if (menu.getItem(i).itemId == menuItemId) {return i}}return INVALID_POSITION}private fun triggerLazyLoad(position: Int) {(fragmentCache[position] as? LazyLoadFragment)?.onLazyLoad()}fun setSmoothScrollEnabled(enabled: Boolean) {isSmoothScrollEnabled = enabled}fun getCurrentFragment(): Fragment? = fragmentCache[currentPosition]private inner class TabPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {override fun getItemCount(): Int = fragmentCache.size()override fun createFragment(position: Int): Fragment {return fragmentCache[position] ?: throw IllegalStateException("Fragment not found at position $position")}}interface LazyLoadFragment {fun onLazyLoad()}companion object {private const val INVALID_POSITION = -1fun create(activity: FragmentActivity,@IdRes viewPagerId: Int,@IdRes bottomNavId: Int): BottomTabHelper {return BottomTabHelper(activity, viewPagerId, bottomNavId)}}
}

使用说明
1. 预定义 Menu 使用方式
步骤 1:定义 Menu 资源

<!-- res/menu/bottom_nav_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:id="@+id/nav_home"android:icon="@drawable/ic_home"android:title="Home"/><itemandroid:id="@+id/nav_search"android:icon="@drawable/ic_search"android:title="Search"/>
</menu>

步骤 2:在布局中绑定 Menu

<com.google.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/bottom_nav"android:layout_width="match_parent"android:layout_height="wrap_content"app:menu="@menu/bottom_nav_menu" />

步骤 3:在 Activity 中绑定 Fragment

class MainActivity : AppCompatActivity() {private lateinit var tabHelper: BottomTabHelperoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)tabHelper = BottomTabHelper.create(activity = this,viewPagerId = R.id.view_pager,bottomNavId = R.id.bottom_nav)// 绑定预定义 Menu 项到 FragmenttabHelper.bindPredefinedTab(R.id.nav_home, HomeFragment())tabHelper.bindPredefinedTab(R.id.nav_search, SearchFragment())}
}

2. 动态添加 Tab (可选)
需要提前在 res/values/ids.xml 中声明id

// 添加动态 Tab (会追加到预定义 Menu 后面)
tabHelper.addTab(BottomTabHelper.TabConfig(menuItemId = R.id.nav_profile, // 需要提前在 res/values/ids.xml 中声明fragment = ProfileFragment(),title = "Profile",iconResId = R.drawable.ic_profile)
)

功能特点
1.混合模式支持

同时支持预定义 Menu 和动态添加 Tab

自动处理位置映射关系

2.生命周期安全:

Fragment 由 ViewPager2 自动管理

支持 LazyLoadFragment 接口实现懒加载

3.配置灵活

可禁用平滑滚动:setSmoothScrollEnabled(false)

随时获取当前 Fragment:getCurrentFragment()

4.性能优化:

使用 SparseArray 存储 Fragment

默认只预加载相邻页面

5.扩展性强

通过 TabConfig 可扩展更多 Tab 属性

易于添加 Badge 等 Material Design 功能

最佳实践建议:
1.对于固定 Tab:使用预定义 Menu + bindPredefinedTab()

2.对于动态 Tab:使用 addTab()

3.需要懒加载:让 Fragment 实现 LazyLoadFragment 接口

4.修改默认动画:在 initBottomNav() 中添加自定义动画逻辑

相关文章:

(Kotlin)Android 高效底部导航方案:基于预定义 Menu 和 ViewPager2 的 Fragment 动态绑定实现

支持预定义 Menu 并绑定 Fragment&#xff0c;同时保留动态添加 Tab 的能力 BottomTabHelper.kt package smartconnection.com.smartconnect.home.utilimport android.content.Context import android.util.SparseArray import androidx.annotation.IdRes import androidx.fra…...

2024年零知识证明(ZK)研究进展

Sumcheck 整个领域正在转向更多地依赖于 Sumcheck Protocol Sumcheck是用于验证多项式承诺的协议&#xff0c;常用于零知识证明&#xff08;ZKP&#xff09;中&#xff0c;尤其是在可验证计算和扩展性上。它的主要目的是通过对多项式进行分段检查&#xff0c;从而保证某个多项…...

AI 驱动的安全分析的价值是什么?

作者&#xff1a;来自 Elastic Riya Juneja, Alyssa VanNice 与 Enterprise Strategy Group 一起量化经济影响 安全行业十分复杂&#xff0c;变化速度极快。攻击面、利益相关者需求、对手战术以及你使用的工具都在不断演变&#xff0c;导致许多安全团队不确定自己是否已做好准备…...

目标检测YOLO实战应用案例100讲-基于孤立森林算法的高光谱遥感图像异常目标检测(续)

目录 3.4 实验结果与分析 3.4.1 数据集介绍 3.4.2 实验参数分析 3.4.3 实验结果评价与讨论 基于高维孤立森林算法的高光谱图像异常检测 4.1 引言 4.2 基于高维孤立森林算法的异常检测模型 4.2.1 面向高维数据的改进策略 4.2.2 基于光谱有效信息率和目标-背景分离…...

flutter框架中文文档,android智能手机编程答案

RecyclerView优化全攻略&#xff1a;从数据处理到性能提升 字节跳动四面有三面都问了这个问题&#xff0c;在此做了整理&#xff0c;希望可以帮助到大家&#xff0c;欢迎查漏补缺。 数据处理和视图加载分离 我们知道&#xff0c;从远端拉取数据肯定是要放在异步的&#xff0…...

AWE 2025:当AI科技遇见智能家居

3月20日&#xff0c;以“AI科技、AI生活”为主题的AWE2025&#xff08;中国家电及消费电子博览会&#xff09;在上海新国际博览中心开幕。作为全球家电行业风向标&#xff0c;本届展会最大的亮点莫过于健康理念在家电领域的全面渗透。从食材保鲜到空气净化&#xff0c;从衣物清…...

Laraver SQL日志 服务开发

Laravel 项目运行中&#xff0c;有时候需要查看sql语句&#xff0c;分析sql运行的耗时&#xff0c;sql语句的复杂程度分析等等 总之&#xff0c;sql的执行在项目中&#xff0c;非常关键&#xff0c;接下来将说明在laravel 中 配置一个sql日志记录服务。 开发思路&#xff1a;…...

NG-ZORRO中tree组件的getCheckedNodeList怎么使用

在 NG-ZORRO&#xff08;Ant Design for Angular&#xff09; 的 Tree 组件 中&#xff0c;getCheckedNodeList 方法用于获取当前选中的节点列表&#xff08;包括半选状态节点&#xff09;。以下是具体用法和示例&#xff1a; 基本用法 首先&#xff0c;确保你已通过 ViewChil…...

win10之mysql server 8.0.41安装

一 mysql server 下载 官网下载地址页面 https://dev.mysql.com/downloads/mysql/二 免装版使用步骤 1 解压 下载完成后,解压文件夹,如下所示: 2 执行安装命令 D:\soft\mysql\mysql-8.0.41-winx64\mysql-8.0.41-winx64\bin>mysqld --install Service successfully in…...

蓝桥杯专项复习——二分

目录 二分查找、二分答案基础知识 二分查找模版 【模版题】数的范围 借教室 二分查找、二分答案基础知识 二分查找模版 【模版题】数的范围 输入样例 6 3 1 2 2 3 3 4 3 4 5输出样例 3 4 5 5 -1 -1 思路&#xff1a; 对应两个模版&#xff0c;起始位置是对应第一个模版…...

oracle中java类的使用

方式一&#xff1a; 编写一个简单的java类 vi OracleJavaDemo.java public class OracleJavaDemo { public static String processData(String input) { return "Processed: " input; } } 编译 javac OracleJavaDemo.java 生成OracleJavaDemo…...

高并发内存池(一):项目介绍和Thread Cache实现

前言&#xff1a;本文将要介绍的高并发内存池&#xff0c;它的原型是Google的⼀个开源项⽬tcmalloc&#xff0c;全称Thread-Caching Malloc&#xff0c;近一个月我将以学习为目的来模拟实现一个精简版的高并发内存池&#xff0c;并对核心技术分块进行精细剖析&#xff0c;分享在…...

MySQL与Redis数据一致性保障方案详解

前言 在现代分布式系统中&#xff0c;MySQL和Redis的结合使用非常普遍。MySQL作为关系型数据库负责持久化存储&#xff0c;而Redis则作为高性能缓存层提升系统的响应速度。然而&#xff0c;在这种架构下&#xff0c;如何保证MySQL与Redis之间的数据一致性是一个重要的挑战。本…...

“钉耙编程”2025春季联赛(2)题解(更新中)

1001 学位运算导致的 1002 学历史导致的 // Problem: 学历史导致的 // Contest: HDOJ // URL: https://acm.hdu.edu.cn/contest/problem?cid1151&pid1002 // Memory Limit: 524288 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.org)#include …...

C#高级:启动、中止一个指定路径的exe程序

一、启动一个exe class Program {static void Main(string[] args){string exePath "D:\测试\Test.exe";// 修改为你要运行的exe路径StartProcess(exePath);}private static bool StartProcess(string exePath){// 创建一个 ProcessStartInfo 对象来配置进程启动参…...

【NumPy】1. 前言安装

0.前言 需要具备[[Python]]基础 定义: NumPy 通常与 [[SciPy]]&#xff08;Scientific Python&#xff09;和 [[Matplotlib]]&#xff08;绘图库&#xff09;一起使用&#xff0c; 这种组合广泛用于替代 [[MatLab]]&#xff0c;是一个强大的科学计算环境&#xff0c;有助于我…...

双向链表的理解

背景 代码中经常会出现双向链表&#xff0c;对于双向链表的插入和删除有对应的API函数接口&#xff0c;但直观的图表更容易理解&#xff0c;所以本文会对rt-thread内核代码中提供的双向链表的一些API函数操作进行绘图&#xff0c;方便后续随时查看。 代码块 rt-thread中提供…...

基于Spring Boot的家庭理财系统app的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

【测试】每日3道面试题 3/31

长期更新&#xff0c;建议关注收藏点赞。 单元测试策略有哪些&#xff0c;主要内容。 白盒测试黑盒测试基于异常和边界的测试 主要内容&#xff1a;测试用例设计、执行、结果分析、自动化beta测试和alpha测试主要区别 主要区别&#xff1a;测试环境测试者 alphabeta时间先后测…...

Go 语言常见错误

代码及工程组织 1、意外的变量隐藏 示例代码&#xff1a; package mainimport ("fmt" )var FunTester "全局变量 FunTester"func main() {FunTester : "局部变量 FunTester"fmt.Println(FunTester) }func showGlobal() {fmt.Println(FunTest…...

216. 组合总和 III 回溯

目录 问题描述 解决思路 关键点 代码实现 代码解析 1. 初始化结果和路径 2. 深度优先搜索&#xff08;DFS&#xff09; 3. 遍历候选数字 4. 递归与回溯 示例分析 复杂度与优化 回溯算法三部曲 1. 路径选择&#xff1a;记录当前路径 2. 递归探索&#xff1a;进入下…...

【Python 算法】动态规划

本博客笔记内容来源于灵神&#xff0c;视频链接如下&#xff1a;https://www.bilibili.com/video/BV16Y411v7Y6?vd_source7414087e971fef9431117e44d8ba61a7&spm_id_from333.788.player.switch 01背包 计算了f[i1]&#xff0c;f[i]就没用了&#xff0c;相当于每时每刻只有…...

nginx的自定义错误页面

正常访问一个不存在的页面是会报404这个错误 我们可以自定义错误页面 error_page 404 /40x.html 然后调用location 最后创建文件 写入你想写的内容 最后实验成功 注意在nginx的配置文件里&#xff0c;注意在加分号 在写完配置时...

制作service列表并打印出来

制作service列表并打印出来 在Linux中&#xff0c;服务&#xff08;Service&#xff09;是指常驻在内存中的进程&#xff0c;这些进程通常监听某个端口&#xff0c;等待其他程序的请求。服务也被称为守护进程&#xff08;Daemon&#xff09;&#xff0c;它们提供了系统所需的各…...

UML 4+1 视图:搭建软件架构的 “万能拼图”

UML 41 视图是一种全面描述软件架构的方法&#xff0c;以下为你详细介绍各个视图&#xff1a; 1.逻辑视图&#xff08;Logical View&#xff09; 概述&#xff1a;逻辑视图主要用于展现系统的功能架构&#xff0c;它聚焦于系统提供的功能以及这些功能的逻辑组织方式&#xff…...

tkinter 库(设计图形界面系统)

几何管理的应用 # tkinter 库 是Python的标准GUI库&#xff0c;提供了创建图形用户界面的功能。 tkinter是一个跨平台的GUI库&#xff0c;支持Windows、macOS和Linux等操作系统。它是Python的标准库之一&#xff0c;无需额外安装。 #tkinter.Entry 是 Tkinter 的输入框控件类&…...

WordPress汉主题

WordPress汉主题wphan.com(以下简称WP汉主题)是一个专注于WordPress中文主题与插件开发的专业团队。该团队致力于为中文用户提供高质量的WordPress主题和插件资源&#xff0c;帮助用户轻松创建专业且吸引人的网站。 WP汉主题提供多种功能丰富的WordPress主题&#xff0c;涵盖博…...

在线文档协作工具选型必看:14款产品对比

本文将深入对比14款在线文档协作工具&#xff1a;PingCode; 2. Worktile; 3. 语雀; 4. 金山文档; 5. WPS云文档; 6. Google Docs; 7. 轻雀文档; 8. Microsoft 365 Online; 9. 明道云文档等。 在数字化办公日益普及的今天&#xff0c;企业对高效协同的需求不断升级&#xff0c;在…...

分布式计算Ray框架面试题及参考答案

目录 简述 Ray 的架构设计核心组件及其协作流程 全局控制存储(GCS)在 Ray 中的作用是什么?如何实现高可用性? 对比 Ray 的任务(Task)与 Actor 模型,说明各自适用场景 解释 Ray 的 Object Store 如何实现跨节点数据共享与零拷贝传输 Ray 的分布式调度器如何实现毫秒级…...

Java虚拟机JVM知识点(持续更新)

JVM内存模型 介绍下内存模型 根据JDK8的规范&#xff0c;我们的JVM内存模型可以拆分为&#xff1a;程序计数器、Java虚拟机栈、堆、元空间、本地方法栈&#xff0c;还有一部分叫直接内存&#xff0c;属于操作系统的本地内存&#xff0c;也是可以直接操作的。 详细解释一下 程…...