HarmonyOS鸿蒙Next开发者技术支持-自定义TabBar页签凸起和凹陷案例
HarmonyOS鸿蒙Next开发者技术支持-自定义TabBar页签凸起和凹陷案例
案例概述
1.1 问题背景
在移动应用开发中,TabBar是常见的底部导航组件。传统的TabBar通常采用平面设计,但为了提升用户体验和视觉吸引力,开发者需要实现以下效果:
- 凸起效果:中间按钮凸起,吸引用户点击
- 凹陷效果:TabBar整体凹陷,增强立体感
- 动态交互:点击动画、悬浮效果
- 状态管理:选中状态、未选中状态区分
- 适配性:兼容不同设备尺寸
1.2 解决方案概述
本案例提供完整的自定义TabBar解决方案,包含:
- 凸起TabBar:中间按钮凸起,带动画效果
- 凹陷TabBar:整体凹陷设计,增强立体感
- 平滑过渡:Tab切换平滑动画
- 状态管理:完整的Tab状态管理
- 主题适配:支持深色/浅色模式
实现步骤详解
步骤1:定义数据模型和配置类
// TabBarModels.ets
export interface TabItem {
id: string; // Tab唯一标识
text: string; // 显示文本
icon: Resource; // 图标资源
activeIcon: Resource; // 激活图标
badge?: number | string; // 角标
disabled?: boolean; // 是否禁用
accessibilityLabel?: string; // 无障碍标签
}
export interface TabBarMetrics {
height: number; // TabBar高度
width: number; // TabBar宽度
itemWidth: number; // 每个Tab宽度
bulgeHeight: number; // 凸起高度
indentDepth: number; // 凹陷深度
safeAreaBottom: number; // 底部安全区域
}
export interface TabAnimationConfig {
duration: number; // 动画时长
curve: number; // 动画曲线
scale: number; // 缩放比例
translateY: number; // Y轴偏移
}
export interface TabStyle {
normalColor: ResourceColor; // 正常颜色
activeColor: ResourceColor; // 激活颜色
backgroundColor: ResourceColor; // 背景色
borderColor: ResourceColor; // 边框颜色
shadowColor: ResourceColor; // 阴影颜色
textSize: number; // 文字大小
iconSize: number; // 图标大小
borderRadius: number; // 圆角半径
}
export class TabBarConfig {
// 凸起TabBar配置
static readonly BULGE_CONFIG = {
height: 80, // 总高度
bulgeHeight: 20, // 凸起高度
bulgeWidth: 60, // 凸起宽度
iconSize: 28, // 图标大小
textSize: 12, // 文字大小
borderRadius: 40, // 圆角半径
shadowBlur: 20, // 阴影模糊
shadowOffsetY: 4, // 阴影偏移
highlightScale: 1.2, // 高亮缩放
};
// 凹陷TabBar配置
static readonly INDENT_CONFIG = {
height: 70, // 总高度
indentDepth: 8, // 凹陷深度
borderWidth: 1, // 边框宽度
iconSize: 24, // 图标大小
textSize: 11, // 文字大小
borderRadius: 20, // 圆角半径
innerPadding: 8, // 内边距
highlightDepth: 4, // 高亮深度
};
// 动画配置
static readonly ANIMATION_CONFIG = {
tabChange: {
duration: 300, // Tab切换动画
curve: Curve.EaseInOut,
},
bulgePress: {
duration: 200, // 凸起按钮按下
curve: Curve.FastOutLinearIn,
},
indentPress: {
duration: 150, // 凹陷按钮按下
curve: Curve.EaseOut,
},
badgeUpdate: {
duration: 300, // 角标更新
curve: Curve.Spring,
},
};
// 样式配置
static readonly STYLE_CONFIG = {
light: {
normalColor: '#666666', // 正常状态颜色
activeColor: '#0066FF', // 激活状态颜色
backgroundColor: '#FFFFFF', // 背景色
borderColor: '#F0F0F0', // 边框颜色
shadowColor: '#40000000', // 阴影颜色
bulgeColor: '#0066FF', // 凸起按钮颜色
bulgeShadow: '#400066FF', // 凸起按钮阴影
},
dark: {
normalColor: '#AAAAAA',
activeColor: '#4D94FF',
backgroundColor: '#1C1C1E',
borderColor: '#2C2C2E',
shadowColor: '#40000000',
bulgeColor: '#4D94FF',
bulgeShadow: '#404D94FF',
},
};
}
步骤1是整个自定义TabBar系统的基础架构设计,我们首先建立了一套完整的数据模型和配置体系。这个步骤的目的是为TabBar的各种状态、样式、动画等提供类型安全的定义和可配置的参数。
步骤2:实现TabBar管理器
// TabBarManager.ets
import { hilog } from '@kit.PerformanceAnalysisKit';
export class TabBarManager {
private static instance: TabBarManager;
private currentTab: string = '';
private previousTab: string = '';
private tabs: Map<string, TabItem> = new Map();
private tabChangeCallbacks: Array<(tabId: string) => void> = [];
private badgeUpdateCallbacks: Array<(tabId: string, badge: number | string) => void> = [];
private animationStateCallbacks: Array<(tabId: string, isAnimating: boolean) => void> = [];
private theme: 'light' | 'dark' = 'light';
private metrics: TabBarMetrics = {
height: 0,
width: 0,
itemWidth: 0,
bulgeHeight: 0,
indentDepth: 0,
safeAreaBottom: 0
};
private constructor() {
this.initialize();
}
public static getInstance(): TabBarManager {
if (!TabBarManager.instance) {
TabBarManager.instance = new TabBarManager();
}
return TabBarManager.instance;
}
private initialize(): void {
this.initThemeListener();
hilog.info(0x0000, 'TabBarManager', 'TabBar管理器初始化完成');
}
private initThemeListener(): void {
// 监听系统主题变化
try {
const context = getContext(this) as common.UIAbilityContext;
// 这里可以添加主题监听逻辑
} catch (error) {
hilog.error(0x0000, 'TabBarManager', '初始化主题监听失败: ' + JSON.stringify(error));
}
}
// 注册Tab
public registerTab(tab: TabItem): void {
this.tabs.set(tab.id, tab);
if (!this.currentTab && this.tabs.size > 0) {
this.currentTab = tab.id;
}
hilog.info(0x0000, 'TabBarManager', `注册Tab: ${tab.id} - ${tab.text}`);
}
// 切换Tab
public switchTab(tabId: string, animated: boolean = true): boolean {
if (!this.tabs.has(tabId)) {
hilog.warn(0x0000, 'TabBarManager', `Tab不存在: ${tabId}`);
return false;
}
const tab = this.tabs.get(tabId)!;
if (tab.disabled) {
hilog.warn(0x0000, 'TabBarManager', `Tab被禁用: ${tabId}`);
return false;
}
if (tabId === this.currentTab) {
return false;
}
this.previousTab = this.currentTab;
this.currentTab = tabId;
hilog.info(0x0000, 'TabBarManager',
`切换Tab: ${this.previousTab} -> ${this.currentTab}, 动画: ${animated}`);
// 通知所有监听者
this.tabChangeCallbacks.forEach(callback => {
callback(tabId);
});
return true;
}
// 更新角标
public updateBadge(tabId: string, badge: number | string): boolean {
if (!this.tabs.has(tabId)) {
return false;
}
const tab = this.tabs.get(tabId)!;
tab.badge = badge;
// 通知角标更新
this.badgeUpdateCallbacks.forEach(callback => {
callback(tabId, badge);
});
hilog.info(0x0000, 'TabBarManager', `更新角标: ${tabId} = ${badge}`);
return true;
}
// 获取当前Tab
public getCurrentTab(): string {
return this.currentTab;
}
// 获取Tab信息
public getTabInfo(tabId: string): TabItem | undefined {
return this.tabs.get(tabId);
}
// 获取所有Tab
public getAllTabs(): TabItem[] {
return Array.from(this.tabs.values());
}
// 设置尺寸信息
public setMetrics(metrics: TabBarMetrics): void {
this.metrics = metrics;
}
// 获取尺寸信息
public getMetrics(): TabBarMetrics {
return this.metrics;
}
// 设置主题
public setTheme(theme: 'light' | 'dark'): void {
this.theme = theme;
hilog.info(0x0000, 'TabBarManager', `切换主题: ${theme}`);
}
// 获取当前主题
public getCurrentTheme(): 'light' | 'dark' {
return this.theme;
}
// 获取主题样式
public getThemeStyle(): TabStyle {
const style = TabBarConfig.STYLE_CONFIG[this.theme];
return {
...style,
textSize: 14,
iconSize: 24,
borderRadius: 20
};
}
// 注册监听器
public onTabChange(callback: (tabId: string) => void): void {
this.tabChangeCallbacks.push(callback);
}
public onBadgeUpdate(callback: (tabId: string, badge: number | string) => void): void {
this.badgeUpdateCallbacks.push(callback);
}
public onAnimationStateChange(callback: (tabId: string, isAnimating: boolean) => void): void {
this.animationStateCallbacks.push(callback);
}
// 通知动画状态
public notifyAnimationState(tabId: string, isAnimating: boolean): void {
this.animationStateCallbacks.forEach(callback => {
callback(tabId, isAnimating);
});
}
// 清理
public destroy(): void {
this.tabs.clear();
this.tabChangeCallbacks = [];
this.badgeUpdateCallbacks = [];
this.animationStateCallbacks = [];
hilog.info(0x0000, 'TabBarManager', 'TabBar管理器已销毁');
}
}
步骤2实现了TabBar的核心管理器,这是一个单例模式的协调者,负责管理所有Tab的状态、协调组件间的通信、处理主题切换等全局功能。
步骤3:实现凸起TabBar组件
// BulgeTabBar.ets
[@Component](/user/Component)
export struct BulgeTabBar {
private tabManager: TabBarManager = TabBarManager.getInstance();
[@State](/user/State) tabs: TabItem[] = [];
[@State](/user/State) currentTab: string = '';
[@State](/user/State) animationStates: Map<string, boolean> = new Map();
[@State](/user/State) bulgeAnimationValue: number = 1;
[@State](/user/State) isThemeDark: boolean = false;
[@Prop](/user/Prop) onTabChange?: (tabId: string) => void;
[@Prop](/user/Prop) selectedColor?: ResourceColor;
[@Prop](/user/Prop) normalColor?: ResourceColor;
[@Prop](/user/Prop) backgroundColor?: ResourceColor;
[@Prop](/user/Prop) bulgeColor?: ResourceColor;
[@Prop](/user/Prop) height: number = TabBarConfig.BULGE_CONFIG.height;
[@Prop](/user/Prop) bulgeHeight: number = TabBarConfig.BULGE_CONFIG.bulgeHeight;
[@Prop](/user/Prop) showDivider: boolean = true;
private bulgeAnimation: AnimationController = new AnimationController({ duration: 200 });
aboutToAppear() {
this.tabs = this.tabManager.getAllTabs();
this.currentTab = this.tabManager.getCurrentTab();
this.setupListeners();
// 初始化动画状态
this.tabs.forEach(tab => {
this.animationStates.set(tab.id, false);
});
}
aboutToDisappear() {
this.bulgeAnimation.reset();
}
private setupListeners(): void {
this.tabManager.onTabChange((tabId: string) => {
this.currentTab = tabId;
});
this.tabManager.onBadgeUpdate((tabId: string, badge: number | string) => {
const index = this.tabs.findIndex(tab => tab.id === tabId);
if (index !== -1) {
this.tabs[index].badge = badge;
this.tabs = [...this.tabs]; // 触发更新
}
});
}
private onTabClick(tab: TabItem, index: number): void {
if (tab.disabled) {
return;
}
// 触发动画
this.triggerTabAnimation(tab.id);
// 中间凸起按钮特殊动画
if (index === Math.floor(this.tabs.length / 2)) {
this.triggerBulgeAnimation();
}
// 切换Tab
if (this.tabManager.switchTab(tab.id, true)) {
this.onTabChange?.(tab.id);
}
}
private triggerTabAnimation(tabId: string): void {
this.animationStates.set(tabId, true);
this.animationStates = new Map(this.animationStates);
this.tabManager.notifyAnimationState(tabId, true);
setTimeout(() => {
this.animationStates.set(tabId, false);
this.animationStates = new Map(this.animationStates);
this.tabManager.notifyAnimationState(tabId, false);
}, TabBarConfig.ANIMATION_CONFIG.tabChange.duration);
}
private triggerBulgeAnimation(): void {
this.bulgeAnimation.value = 1;
this.bulgeAnimation.play();
}
[@Builder](/user/Builder)
private buildTabItem(tab: TabItem, index: number) {
const isActive = tab.id === this.currentTab;
const isAnimating = this.animationStates.get(tab.id) || false;
const isMiddleTab = index === Math.floor(this.tabs.length / 2);
const themeStyle = this.tabManager.getThemeStyle();
const config = TabBarConfig.BULGE_CONFIG;
if (isMiddleTab) {
// 中间凸起按钮
this.buildBulgeTab(tab, isActive, isAnimating, themeStyle);
} else {
// 普通Tab按钮
this.buildNormalTab(tab, isActive, isAnimating, themeStyle);
}
}
[@Builder](/user/Builder)
private buildNormalTab(tab: TabItem, isActive: boolean, isAnimating: boolean, style: TabStyle) {
const config = TabBarConfig.BULGE_CONFIG;
const scale = isAnimating ? config.highlightScale : 1;
const opacity = isAnimating ? 0.8 : 1;
const translateY = isAnimating ? -5 : 0;
Column() {
// 角标
if (tab.badge !== undefined) {
this.buildBadge(tab.badge);
}
// 图标
Stack({ alignContent: Alignment.Center }) {
Image(isActive ? tab.activeIcon : tab.icon)
.width(config.iconSize)
.height(config.iconSize)
.objectFit(ImageFit.Contain)
.interpolation(ImageInterpolation.High)
}
.width(config.iconSize + 20)
.height(config.iconSize + 20)
.borderRadius((config.iconSize + 20) / 2)
.backgroundColor(isActive ? `${style.activeColor}20` : Color.Transparent)
// 文本
Text(tab.text)
.fontSize(config.textSize)
.fontColor(isActive ? style.activeColor : style.normalColor)
.fontWeight(isActive ? FontWeight.Medium : FontWeight.Normal)
.margin({ top: 4 })
.opacity(isActive ? 1 : 0.8)
}
.scale({ x: scale, y: scale })
.opacity(opacity)
.translate({ y: translateY })
.animation({
duration: TabBarConfig.ANIMATION_CONFIG.tabChange.duration,
curve: TabBarConfig.ANIMATION_CONFIG.tabChange.curve
})
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.padding({ top: 8, bottom: 8 })
.onClick(() => this.onTabClick(tab, index))
}
[@Builder](/user/Builder)
private buildBulgeTab(tab: TabItem, isActive: boolean, isAnimating: boolean, style: TabStyle) {
const config = TabBarConfig.BULGE_CONFIG;
const bulgeScale = isAnimating ? 1.1 : 1;
const shadowOpacity = isAnimating ? 0.6 : 0.4;
const translateY = isAnimating ? -config.bulgeHeight * 0.5 : -config.bulgeHeight * 0.3;
Column() {
// 凸起背景
Circle()
.width(config.iconSize + 40)
.height(config.iconSize + 40)
.fill(this.bulgeColor || style.bulgeColor)
.shadow({
radius: config.shadowBlur,
color: style.bulgeShadow,
offsetY: config.shadowOffsetY
})
更多关于HarmonyOS鸿蒙Next开发者技术支持-自定义TabBar页签凸起和凹陷案例的实战教程也可以访问 https://www.itying.com/category-93-b0.html
学习
更多关于HarmonyOS鸿蒙Next开发者技术支持-自定义TabBar页签凸起和凹陷案例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
鸿蒙Next自定义TabBar页签凸起和凹陷可通过ArkUI组件实现。使用@Builder装饰器构建自定义TabBar,结合Stack和Positioned布局控制凸起位置。通过borderRadius和clip属性设置圆角与裁剪效果,利用offset或margin调整凸起高度。凹陷效果可使用Column嵌套并设置内边距或背景色差实现。具体样式需在aboutToAppear生命周期中动态计算位置。


