HarmonyOS 多设备短视频开发 : 16 — Tabs 组件与 MSVTabs 封装
16 — Tabs 组件与 MSVTabs 封装
一、引言

短视频应用的核心交互是"多页签切换":首页、朋友、发布、消息、我的。HarmonyOS 提供 Tabs/TabContent 组件承载这一能力,但若每个页面各自维护一套 TabBar 样式,必然导致视觉漂移与维护成本上升。本项目在 common 层封装了 MSVTabs 公共组件,统一手机端底部页签与首页次级页签的样式交互,同时为 TV 智慧屏单独实现 TvTabs。本文结合 common/multishortvideobase/src/main/ets/components/MSVTabs.ets、products/tv/src/main/ets/components/TvTabs.ets 讲解封装思路与多设备差异化。
二、Tabs/TabContent 基础用法
Tabs 由"页签栏 + 内容区"组成,TabContent 承载页面内容,通过 tabBar 属性自定义页签 UI:
Tabs({ index: this.activeIndex }) {
TabContent() { HomePage() }.tabBar(this.MyTabBar(0))
TabContent() { MinePage() }.tabBar(this.MyTabBar(1))
}
.barPosition(BarPosition.End) // 页签位于底部
.barOverlap(true) // 页签悬浮于内容之上
.onChange((index: number) => { this.activeIndex = index; })常用属性:barPosition 控制页签栏位置(本项目底部 End / 顶部 Start)、barOverlap 控制页签是否悬浮(true)、barMode 控制 Fixed/Scrollable(BarMode.Scrollable)、barHeight 控制高度(56vp)。
三、MSVTabs 公共组件封装
MSVTabs 以数据驱动页签,配置收敛在 MSVDataModel(内容 Builder、文本、图标、是否仅图标):
export class MSVDataModel {
public content?: BuilderCallback | null; // 页签内容
public text?: ResourceStr; // 页签文字
public iconOnly?: boolean; // 是否纯图标
public icon?: Resource;
public iconDark?: Resource; // 深色图标
public iconSize?: Length;
}组件对外参数(MSVTabs.ets):
@ComponentV2
export struct MSVTabs {
@Require @Param data: MSVDataModel[]; // 必传
@Local activeIndex: number = 0; // 内部选中态
@Param useFloating: boolean = true; // 悬浮 TabBar
@Param index: number = 0; // 初始索引
@Param showBarUnderline: boolean = false; // 选中下划线
@Param barPosition: BarPosition = BarPosition.Start;
@Param onIndexChange: (index: number) => void = () => {};
aboutToAppear(): void { this.activeIndex = this.index; }
}@Require @Param 强制调用方传 data;@Local activeIndex 内部维护选中态;onIndexChange 把切换事件抛给外部(如切到"我的"时切换深浅色)。
四、自定义 TabBar 构建
MSVTabs 用 @Builder 定义页签样式,区分"纯图标"与"图标+文字",支持选中下划线:
@Builder
MSVTabBar(params: MSVDataModel, currentIndex: number) {
if (params.iconOnly) {
Image(this.isDark ? params.iconDark : params.icon)
.width(params.iconSize).aspectRatio(1)
} else {
Column() {
Text(params.text)
.fontColor(this.activeIndex === currentIndex ? this.selectedLightColor : this.lightColor)
}
.border({ width: { bottom: this.showBarUnderline && (this.activeIndex === currentIndex) ? 1 : 0 } })
}
}MainTabsViewModel 中"发布"页签即纯图标形态(加号):
this.mainTabsData.push(new MSVDataModel(home, $r('app.string.home_title')));
this.mainTabsData.push(new MSVDataModel(add, '', true, $r('app.media.ic_plus'), $r('app.media.ic_plus_dark')));其余"朋友、消息、我的"为图文形态,仅传文本与内容 Builder。
五、HdsTabs 悬浮页签与降级策略
大屏启用系统 HDS 悬浮页签,小屏或不支持时降级为原生 Tabs,用 canIUse 能力探测 + useFloating 开关双轨控制:
if (canIUse('SystemCapability.UIDesign.HDSComponent.Core') && this.useFloating) {
HdsTabs({ index: this.activeIndex }) { this.MSVTabContent(this.data) }
.barPosition(BarPosition.End)
.barFloatingStyle({ barSideMargin: 16, barBottomMargin: this.barBottomMargin, ... })
.barHeight(44)
} else {
Tabs({ index: this.activeIndex }) { this.MSVTabContent(this.data) }
.barPosition(this.barPosition)
.barBackgroundBlurStyle(BlurStyle.NONE)
.barHeight(56).barMode(BarMode.Scrollable, { margin: 8 })
}首页次级页签 SubTabs(SubTabsComponent.ets)关闭悬浮并开启下划线:useFloating: false, index: 4, showBarUnderline: true,数据来自 SubTabsViewModel(商城、直播、关注、推荐等)。
六、TV 端 TvTabs 差异化
TV 端无触摸,TvTabs 用横向 List 构建页签,选中态由全局 currentBottomIndex 驱动,天然支持遥控器聚焦:
@ComponentV2
export struct TVTabs {
@Consumer('currentBottomIndex') currentBottomIndex: number = 2;
@Local btnList: Array<Resource> = [推荐, 关注, 朋友, 加号, 消息, 我的];
// 横向 List 渲染,当前项高亮背景与文字色
}与 MSVTabs 的差异对比如下:
| 维度 | MSVTabs(手机/平板) | TvTabs(智慧屏) |
| 容器 | Tabs / HdsTabs | 横向 List |
| 位置 | 底部悬浮或顶部 | 顶部居中胶囊 |
| 交互 | 点击、滑动 | 遥控器方向键 + 点击 |
| 状态 | @Param + @Local | @Consumer 全局索引 |
七、总结与最佳实践
- 用数据模型驱动页签,新增页签只加一条数据,内容与图标统一收敛。
- 参数化 + @Builder 自定义 TabBar,一套组件覆盖底部、顶部、纯图标、下划线多种形态。
- canIUse 探测 + useFloating 开关实现 HDS 悬浮与原生 Tabs 双轨降级。
- 设备差异收敛在产品层:手机/平板复用 MSVTabs,TV 独立实现 TvTabs。
- onIndexChange 只抛事件不写业务,保证公共组件与业务解耦。
更多推荐




所有评论(0)