Cocos Creator 3.x中实现底部导航栏Tab切换逻辑

在 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;
        }
    }
}

三、在编辑器中绑定组件

  1. MainTabManager.ts 挂载到 BottomNav 节点上
  2. 拖拽四个 View 节点到 viewList 数组槽
    • 顺序必须是:[HomeView, LeagueView, AlbumView, BagView]
  3. 拖拽四个 Tab 按钮到 tabButtons 数组槽
    • 顺序必须是:[HomeTab, LeagueTab, AlbumTab, BagTab]

四、Tab 按钮视觉反馈(两种方案)

方案 A:用两套图片(推荐)

  • 准备 8 张图:
    • tab_home_normal.png / tab_home_active.png
    • tab_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
        }
    }
}

五、优化建议

  1. 避免重复加载
    onViewShow 中加判断,只在首次进入时请求数据: private loadedTabs: Set<TabType> = new Set(); onViewShow(tab: TabType) { if (!this.loadedTabs.has(tab)) { this.loadData(tab); this.loadedTabs.add(tab); } }
  2. 添加切换动画
    使用 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();
  3. 支持返回首页
    在其他界面按手机返回键时,先切回首页: // 在 LeagueView 脚本中 onBackButtonPressed() { mainTabManager.switchToTab(TabType.HOME); return true; // 拦截返回 }

最终效果

  • 点击「联赛」→ 联赛界面出现,联赛 Tab 高亮
  • 再点「首页」→ 抽卡界面恢复,首页 Tab 高亮
  • 切换过程流畅,无白屏或错乱

Comments

No comments yet. Why don’t you start the discussion?

发表回复