HarmonyOS掌上记账APP开发实践第96篇:Tab组件如何实现增删Tab标签
Tab组件如何实现增删Tab标签场景介绍部分应用在使用过程中需要自定义添加或删除标签的场景比如在浏览器中的顶部标签栏中需要新打开或关闭网页页签而这种场景与Tabs组件效果类似但Tabs组件不提供增加或删除页签的功能不能自由的增加删除页签需要开发者自己实现Tabs中增删页签功能。本文以浏览器中增加或删除页签为例实现Tabs中页签的增删功能。效果呈现如下动图所示实现原理本例涉及的关键特性以及实现方案如下通过Builder自定义封装一个导航页签栏并通过ForEach完成对Tabs组件的内容页和导航页签栏的动态渲染。通过TabsController的changeIndex可实现页面的跳转传入的index是数组中对应的索引值。页签的增加通过数组的push方法增加数组元素。删除页签通过删除页面对应数组的索引值处的数据完成删除后页面跳转位置根据业务逻辑要求确定跳转页面对应的索引值。开发步骤整体布局分为两部分页面内容和页签部分。页面视图部分使用Tabs其中页签对应显示的内容需要放入TabContent组件中。页签部分通过Builder自定义封装一个tabBar组件放入Tabs中的tabBar属性中。页面视图代码块Tabs({barPosition:BarPosition.Start,controller:this.controller}){ForEach(this.tabArray,(item:number){TabContent(){Text(我是页面 item 的内容).height(100%).width(100%).fontSize(30).backgroundColor(#ffffffff)}.tabBar(this.Tab(item))},(item:number)item.toString()util.generateRandomUUID())}.barMode(BarMode.Scrollable,{margin:30}).onChange((index){this.focusIndexindex}).barHeight(30)自定义tabBar代码块//控制页签渲染的数组StatetabArray:Arraynumber[0]//Tabs组件当前显示的页签StatefocusIndex:number0//创建页签时的页签indexStatepre:number-1//Tabs组件控制器根据组件下标控制tab跳转privatecontroller:TabsControllernewTabsController()//自定义导航页签栏BuilderTab(tabNumber:number){Row({space:20}){Text(页签 tabNumber).fontSize(18)Image($r(app.media.ic_public_cancel_filled)).width(20).height(20)}.justifyContent(FlexAlign.Center).constraintSize({minWidth:35}).width(120).height(30).borderRadius({topLeft:10,topRight:10}).backgroundColor(this.tabArray.indexOf(tabNumber)this.focusIndex?#ffffffff:#ffb7b7b7)}实现页签和页面视图的联动这里主要通过TabsController的changeIndex来实现对应的试图跳转但需要注意由于之后会增删数组元素的操作所以此处传入的index值是选择页面的tabNumber在数组中的索引值。this.focusIndexthis.tabArray.indexOf(tabNumber)增添数组元素实现增加页签的效果增添数组元素使用数组的push方法在tabArray添加数据即可但由于此demo原始定义的数组是连续的自然数后续增删数组会打乱原有顺序所以此处处理为先判断最后一个元素的值再加1由于TabBar的渲染是通过ForEach被State修饰的数组因此当tabArray中添加数据后系统会通知ForEach便利数组重新渲染页面。this.tabArray.push(this.tabArray[this.tabArray.length-1]1)通常在添加新页面后浏览器会将页面跳转到新添加的页面因此在向tabArray中完成数据推送后需要将页签通过TabsController中的changeIndex方法跳转到最后一个。this.focusIndexthis.tabArray.length-1this.controller.changeIndex(this.focusIndex)当用户选择另一个页签时可通过自定义页签中通用事件onClick进行控制当用户点击待选择的页签后获取当前页签对应的下标然后通过TabsController中的changeIndex方法进行跳转,此外可以通过更改页签背景颜色标识被选中页签的。.backgroundColor(this.tabArray.indexOf(tabNumber)this.focusIndex?#ffffffff:#ffb7b7b7).onClick((){this.focusIndexthis.tabArray.indexOf(tabNumber)this.controller.changeIndex(this.focusIndex)})删除页面有三种情况删除不同位置的页面。第一种情况是被关闭页面为最后一个页面且当前选中的页面为最后一个页面如果当前被选中页面是刚刚被其他页面创建的情况因此页面需要跳回到创建被删除页签的页签逻辑参考chrome浏览器if(this.pre0){this.focusIndexthis.pre}this.tabArray.splice(this.tabArray.indexOf(tabNumber),1)如果不是的话直接将当前显示页签下下标设置为前一个页签下标tabArray数组通过splice方法删除页签并通过TabsController完成跳转此外页面只要有关闭操作页面就不可以跳回打开该页面的页面即将 pre设置为-1。this.focusIndexthis.focusIndex-1this.tabArray.splice(this.tabArray.indexOf(tabNumber),1)this.pre-1this.controller.changeIndex(this.focusIndex)第二种情况当用户当前选择的不是最后一个标签然后直接删除其他页签时可以直接删除但是需要重新计算当前选中页签在tabArray中的实时位置到新注意需要排除用户在最后一个页签删除当前页签的情况。//当前选择页面的对应数组中的数据值letfocusNumberthis.tabArray[this.focusIndex]//用于判断是否是用户在最后一个页签删除当前页签的情况if(this.tabArray.indexOf(focusNumber)0){this.focusIndexthis.tabArray.indexOf(focusNumber)}this.controller.changeIndex(this.focusIndex)第三种情况当用户当前选择的不是最后一个标签且删除被选中页面直接删除然后通过TabsController完成跳转不需要额外操作。注意由于Tabs组件中的导航页签栏会占满屏幕导致添加按钮无法直接添加到与页签直接平齐的位置因此通过层叠布局Stack的方式将添加页签按钮覆盖到Tabs组件上通过Stack中的对齐方式将添加按钮调整到合适位置。用于Tabs添加或删除子节点时ForEach需要重新将所有页签进行重新渲染如果在添加或删除完页签后直接调用TabsController中的changeIndex进行跳转页面无法调到指定页签。这是由于ForEach还未将组件渲染完成将子组件挂载到Tabs中因此建议通过setTimeOut延迟一段时间再进行跳转经过验证大概50ms后即可开发者可再自行验证。setTimeout((){this.controller.changeIndex(this.focusIndex)},50)完整实例完整示例代码如下importutilfromohos.utilEntryComponentstruct Drag{//控制页签渲染的数组StatetabArray:Arraynumber[0]//Tabs组件当前显示的页签下标StatefocusIndex:number0//创建页签时的页签indexStatepre:number-1//Tabs组件控制器根据组件下标控制tab跳转privatecontroller:TabsControllernewTabsController()// 单独的页签BuilderTab(tabNumber:number){Row({space:20}){Text(页签 tabNumber).fontSize(18)Image($r(app.media.ic_public_cancel_filled)).width(20).height(20).onClick((){//获取Tabs组件当前显示的页签中显示的数字letfocusNumberthis.tabArray[this.focusIndex]//被删除的页签是否是当前选中的页签且是最后一个页签if(this.focusIndexthis.tabArray.indexOf(tabNumber)this.focusIndexthis.tabArray.length-1){//判断是否需要跳回到创建该页签时的页签如果不需要直接跳转到前一个页签if(this.pre0){this.focusIndexthis.pre}else{this.focusIndexthis.focusIndex-1}}this.tabArray.splice(this.tabArray.indexOf(tabNumber),1)this.pre-1//对应删除页面中的第二种情况if(this.tabArray.indexOf(focusNumber)0){this.focusIndexthis.tabArray.indexOf(focusNumber)}//设置50ms 延迟跳转setTimeout((){this.controller.changeIndex(this.focusIndex)},50)})}.justifyContent(FlexAlign.Center).constraintSize({minWidth:35}).width(120).height(30).borderRadius({topLeft:10,topRight:10}).backgroundColor(this.tabArray.indexOf(tabNumber)this.focusIndex?#ffffffff:#ffb7b7b7).onClick((){this.focusIndexthis.tabArray.indexOf(tabNumber)setTimeout((){this.controller.changeIndex(this.focusIndex)},50)})}build(){Column(){Column(){// 页签// Row() {Stack(){Row({space:7}){//tabsTabs({barPosition:BarPosition.Start,controller:this.controller}){ForEach(this.tabArray,(item:number){TabContent(){Text(我是页面 item 的内容).height(100%).width(100%).fontSize(30).backgroundColor(#ffffffff)}.tabBar(this.Tab(item))},(item:number)item.toString()util.generateRandomUUID())}.barMode(BarMode.Scrollable,{margin:30}).onChange((index){this.focusIndexindex}).barHeight(30)}.width(100%)Row(){Image($r(app.media.ic_public_add_filled)).onClick((){if(this.tabArray.length0){this.tabArray.push(0)this.focusIndexthis.tabArray.length-1}else{this.prethis.focusIndexthis.tabArray.push(this.tabArray[this.tabArray.length-1]1)this.focusIndexthis.tabArray.length-1}setTimeout((){this.controller.changeIndex(this.focusIndex)},50)}).width(20).height(20)}.height(30).width(30).backgroundColor(#ffb7b7b7).justifyContent(FlexAlign.Center)}.alignContent(Alignment.TopEnd).width(100%).backgroundColor(#ffb7b7b7)}.alignItems(HorizontalAlign.Start).width(100%)}.height(100%)}}参考TabsState装饰器组件内状态ForEach循环渲染层叠布局Stack

相关新闻

Unity UGUI布局冲突解决方案:Content Size Fitter与LayoutGroup兼容性实战

Unity UGUI布局冲突解决方案:Content Size Fitter与LayoutGroup兼容性实战

1. 项目概述:一个困扰无数Unity UI开发者的“经典”难题如果你在Unity里做过稍微复杂一点的UI界面,尤其是那种需要动态调整尺寸、自适应内容的列表或面板,那你大概率遇到过这个让人头疼的报错。当你试图在一个带有LayoutGroup(比如…

2026/7/31 15:56:24 阅读更多 →
家长都在选什么教材同步课辅导的软件?一文对比百分书童、作业帮、小猿搜题等热门学习APP

家长都在选什么教材同步课辅导的软件?一文对比百分书童、作业帮、小猿搜题等热门学习APP

随着新课标的实施,越来越多家长开始意识到,仅仅依靠课堂学习已经无法满足孩子的学习需求。尤其是在课前预习、课后复习、家庭作业辅导等环节,拥有一款真正教材同步课辅导软件,不仅能够帮助孩子提高学习效率,也能减轻家…

2026/7/30 14:29:53 阅读更多 →
一个APP,覆盖预习、学习、复习、练习全流程——百分书童重新定义教材同步学习

一个APP,覆盖预习、学习、复习、练习全流程——百分书童重新定义教材同步学习

近几年,教育行业发生了明显变化。早期的教育App,大多依靠拍题、搜题、直播课程吸引用户;后来,AI技术的发展让智能答疑、作文批改、口语练习等功能逐渐普及。但随着市场竞争不断加剧,单一功能已经难以满足用户需求&…

2026/7/30 14:29:53 阅读更多 →

最新新闻

2026 珠三角 3C 磁吸支架转轴源头供应商盘点 附选型指南

2026 珠三角 3C 磁吸支架转轴源头供应商盘点 附选型指南

东莞松亿电子 本文为行业信息整理,按服务能力与业务范围分类盘点,非官方排名,仅作选型参考,不构成采购推荐。 行业背景说明 转轴磁吸手机支架的上游供应链高度集中在珠三角(东莞、深圳、佛山)产业带&…

2026/7/31 15:56:16 阅读更多 →
Python测试框架pytest核心特性与实战技巧

Python测试框架pytest核心特性与实战技巧

1. pytest框架概述与核心价值 pytest作为Python生态中最主流的测试框架之一,其设计哲学与传统的unittest有着本质区别。我在实际企业级测试实践中发现,pytest通过极简的语法结构和强大的插件机制,能够将测试代码的编写效率提升3-5倍。其核心优…

2026/7/31 15:56:16 阅读更多 →
180秒魔法转换:GitHub Desktop中文汉化工具让代码管理更亲切

180秒魔法转换:GitHub Desktop中文汉化工具让代码管理更亲切

180秒魔法转换:GitHub Desktop中文汉化工具让代码管理更亲切 【免费下载链接】GitHubDesktop2Chinese GithubDesktop语言本地化(汉化)工具 【GitHub桌面客户端中文汉化】 项目地址: https://gitcode.com/gh_mirrors/gi/GitHubDesktop2Chinese 你是否曾经面对…

2026/7/31 15:56:16 阅读更多 →
大数据转大模型:权限与日志才是Demo到生产的生死线

大数据转大模型:权限与日志才是Demo到生产的生死线

如果你正准备往大模型方向转,《别急着换赛道:大数据经验在 AI 项目里到底值多少?》这类问题别只看热度。更重要的是判断自己该补哪块能力,以及怎么证明你真的会。摘要摘要:从大数据到大模型,数据工程师的迁…

2026/7/31 15:56:16 阅读更多 →
Python实现B站视频下载的完整指南:解锁大会员4K与充电专属内容

Python实现B站视频下载的完整指南:解锁大会员4K与充电专属内容

Python实现B站视频下载的完整指南:解锁大会员4K与充电专属内容 【免费下载链接】bilibili-downloader B站视频下载,支持下载大会员清晰度4K,持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 你是否曾想…

2026/7/31 15:56:16 阅读更多 →
GitHub热门项目解析:分布式计算与AI代码助手趋势

GitHub热门项目解析:分布式计算与AI代码助手趋势

1. 项目概述 2026年1月18日GitHub热门项目榜单反映了当前技术社区的最新趋势和开发者关注焦点。作为技术从业者,定期分析GitHub趋势项目能帮助我们把握技术发展方向,发现潜在的工具链优化机会。 2. 核心项目解析 2.1 分布式计算框架Nebula 3.0 这个来…

2026/7/31 15:55:15 阅读更多 →

日新闻

物理复制比逻辑复制好在哪?数据库复制原理详解

物理复制比逻辑复制好在哪?数据库复制原理详解

数据库复制是把主库数据同步到备库的机制,分为逻辑复制和物理复制两种。逻辑复制传输的是 SQL 语句或行变更事件,物理复制传输的是存储引擎底层的物理日志。阿里云 PolarDB(云原生数据库)采用物理复制,在同步延迟、数据…

2026/7/31 0:00:34 阅读更多 →
BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bilib…

2026/7/31 0:00:34 阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

当前,游戏行业的“DataAI融合”已从概念验证进入价值落地阶段。根据IDC 2025年数据,中国AI游戏云市场规模已达18.6亿元;同时,游戏研发环节AI渗透率高达86%,生成式AI内容普及率超过50%。面对庞大的市场,游戏…

2026/7/31 0:00:34 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/31 1:03:03 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/31 4:19:39 阅读更多 →

月新闻