在 Cocos Creator 3.x 中实现 底部导航栏 Tab 切换逻辑,核心思路是:
点击某个 Tab → 高亮该 Tab → 隐藏其他界面 → 显示对应界面
下面我给你一套 完整、可复用、带状态管理 的实现方案。
一、UI 层级准备(关键!)
确保你的场景结构如下:
Canvas
└── MainPanel
├── HomeView // 首页界面(抽卡)
├── LeagueView // 联赛界面(初始隐藏)
├── AlbumView // 图鉴界面(初始隐藏)
├── BagView // 背包界面(初始隐藏)
└── BottomNav
├── HomeTab
├── LeagueTab
├── AlbumTab
└── BagTab
重要:
- 所有
*View节点必须是 同级兄弟节点- 初始时只有
HomeView.active = true,其余设为false
二、创建 Tab 管理脚本(MainTabManager.ts)
// MainTabManager.ts
import { _decorator, Component, Node, Button, Sprite, UITransform } from 'cc';
const { ccclass, property } = _decorator;
// 定义 Tab 类型
enum TabType {
HOME = 0,
LEAGUE = 1,
ALBUM = 2,
BAG = 3
}
@ccclass('MainTabManager')
export class MainTabManager extends Component {
// 拖拽绑定:四个 View 界面
@property({ type: [Node] })
viewList: Node[] = [];
// 拖拽绑定:四个 Tab 按钮
@property({ type: [Button] })
tabButtons: Button[] = [];
// 当前激活的 Tab
private currentTab: TabType = TabType.HOME;
start() {
this.initTabs();
}
// 初始化 Tab 点击事件
initTabs() {
for (let i = 0; i < this.tabButtons.length; i++) {
const btn = this.tabButtons[i];
const tabIndex = i as TabType;
// 绑定点击事件(使用闭包捕获 i)
btn.node.on(Button.EventType.CLICK, () => {
this.switchToTab(tabIndex);
}, this);
}
// 默认激活首页
this.switchToTab(TabType.HOME);
}
// 切换到指定 Tab
switchToTab(targetTab: TabType) {
// 如果已经是当前 Tab,不重复切换
if (this.currentTab === targetTab) return;
// 1. 隐藏所有 View
this.viewList.forEach(view => view.active = false);
// 2. 显示目标 View
this.viewList[targetTab].active = true;
// 3. 更新 Tab 按钮状态
this.updateTabButtons(targetTab);
// 4. 记录当前 Tab
this.currentTab = targetTab;
// 5. 【可选】触发界面初始化逻辑
this.onViewShow(targetTab);
}
// 更新按钮高亮状态
updateTabButtons(activeTab: TabType) {
for (let i = 0; i < this.tabButtons.length; i++) {
const btn = this.tabButtons[i];
const sprite = btn.getComponent(Sprite);
if (!sprite) continue;
// 根据是否激活,切换 SpriteFrame
// 假设你有两套图:normal 和 active
const isActive = (i === activeTab);
sprite.spriteFrame = isActive
? this.getActiveSpriteFrame(i)
: this.getNormalSpriteFrame(i);
}
}
// 【可选】根据 Tab 类型返回高亮图标
getActiveSpriteFrame(index: number): any {
// 你可以在这里根据 index 返回不同的高亮图
// 或者统一用一个方法,比如从资源动态加载
return null; // 实际项目中替换为你的资源
}
// 【可选】根据 Tab 类型返回普通图标
getNormalSpriteFrame(index: number): any {
return null;
}
// 【可选】界面显示时的回调(用于懒加载数据)
onViewShow(tab: TabType) {
switch (tab) {
case TabType.LEAGUE:
// 联赛界面:请求排行榜数据
console.log("加载联赛数据...");
break;
case TabType.ALBUM:
// 图鉴界面:刷新角色收集进度
console.log("刷新图鉴...");
break;
case TabType.BAG:
// 背包界面:加载道具列表
console.log("加载背包...");
break;
}
}
}
三、在编辑器中绑定组件
- 将
MainTabManager.ts挂载到BottomNav节点上 - 拖拽四个 View 节点到
viewList数组槽- 顺序必须是:
[HomeView, LeagueView, AlbumView, BagView]
- 顺序必须是:
- 拖拽四个 Tab 按钮到
tabButtons数组槽- 顺序必须是:
[HomeTab, LeagueTab, AlbumTab, BagTab]
- 顺序必须是:
四、Tab 按钮视觉反馈(两种方案)
方案 A:用两套图片(推荐)
- 准备 8 张图:
tab_home_normal.png/tab_home_active.pngtab_league_normal.png/tab_league_active.png- …
- 在
getNormalSpriteFrame()和getActiveSpriteFrame()中返回对应资源
方案 B:用颜色/缩放变化(简单)
修改 updateTabButtons 方法:
updateTabButtons(activeTab: TabType) {
for (let i = 0; i < this.tabButtons.length; i++) {
const btn = this.tabButtons[i];
const transform = btn.node.getComponent(UITransform);
if (i === activeTab) {
// 高亮:放大 + 白色
transform.setContentSize(130, 90); // 原尺寸 120x80
// 如果有 Label,设置 color = Color.WHITE
} else {
// 普通:缩小 + 灰色
transform.setContentSize(120, 80);
// Label color = Color.GRAY
}
}
}
五、优化建议
- 避免重复加载
在onViewShow中加判断,只在首次进入时请求数据:private loadedTabs: Set<TabType> = new Set(); onViewShow(tab: TabType) { if (!this.loadedTabs.has(tab)) { this.loadData(tab); this.loadedTabs.add(tab); } } - 添加切换动画
使用tween淡入淡出:// 隐藏旧界面 tween(oldView).to(0.2, { opacity: 0 }).call(() => oldView.active = false).start(); // 显示新界面 newView.opacity = 0; newView.active = true; tween(newView).to(0.2, { opacity: 255 }).start(); - 支持返回首页
在其他界面按手机返回键时,先切回首页:// 在 LeagueView 脚本中 onBackButtonPressed() { mainTabManager.switchToTab(TabType.HOME); return true; // 拦截返回 }
最终效果
- 点击「联赛」→ 联赛界面出现,联赛 Tab 高亮
- 再点「首页」→ 抽卡界面恢复,首页 Tab 高亮
- 切换过程流畅,无白屏或错乱