This commit is contained in:
2025-10-13 17:09:39 +08:00
parent 347a7292cc
commit 104e7f566c
9 changed files with 376 additions and 230 deletions
@@ -169,15 +169,21 @@ export class NavigationManager {
const hideDirection = isMovingRight ? "left" : "right";
const showDirection = isMovingRight ? "right" : "left";
// 隐藏当前面板(包括子页面
// 清空当前面板子页面
if (this.currentActivePanelType) {
logger.log(
`[NavigationManager] 切换主面板,清空当前面板的子页面栈: ${this.currentActivePanelType}`
);
this.clearSubPanelStack(this.currentActivePanelType, true);
}
// 隐藏当前面板
if (this.currentActivePanel && this.currentActivePanel.isValid) {
this.hidePanelWithAnimation(this.currentActivePanel, hideDirection);
}
// 加载并显示目标面板
this.loadOrGetPanel(panelType, (panel: Node) => {
// 清理当前面板的subPanel(在hidePanelWithAnimation中已经清理,这里不需要重复)
this.currentActivePanelType = panelType;
// 更新NavigationPanel按钮状态
@@ -192,8 +198,11 @@ export class NavigationManager {
});
}
private subPanelDic: Map<Node, Node[]> = new Map();
// 每个主面板的子页面栈(按打开顺序,最后一个元素是栈顶)
private subPanelStacks: Map<PanelType, Node[]> = new Map();
// 子面板到主面板类型的映射
private subPanelMapping: Map<Node, PanelType> = new Map();
// 子面板的动画配置
private subPanelAnimationConfigs: Map<Node, SubPanelAnimationConfig> =
new Map();
@@ -206,114 +215,6 @@ export class NavigationManager {
// 动画锁,防止动画中重复操作
private isAnimating: boolean = false;
/**
* 带动画的子面板清理方法
* @param basePanel 主面板节点
* @param callback 所有动画完成后的回调
*/
private clearSubPanelsWithAnimation(
basePanel: Node,
callback?: Function
): void {
if (!basePanel || !basePanel.isValid) {
callback && callback();
return;
}
const subPanels = this.subPanelDic.get(basePanel);
if (!subPanels || subPanels.length === 0) {
callback && callback();
return;
}
let completedCount = 0;
const totalCount = subPanels.length;
// 完成动画的回调
const onAnimationComplete = () => {
completedCount++;
if (completedCount >= totalCount) {
// 清理subPanelDic
this.subPanelDic.delete(basePanel);
callback && callback();
}
};
// 同时播放所有子面板的关闭动画
for (let i = subPanels.length - 1; i >= 0; i--) {
const subPanel = subPanels[i];
if (subPanel && subPanel.isValid) {
// 获取动画配置
let config = this.subPanelAnimationConfigs.get(subPanel);
if (!config) {
config = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.2,
};
}
// 播放关闭动画
this.playSubPanelCloseAnimation(subPanel, config, () => {
// 清理映射
this.subPanelMapping.delete(subPanel);
this.subPanelAnimationConfigs.delete(subPanel);
// 销毁节点
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
baseView.close();
} else {
subPanel.destroy();
}
onAnimationComplete();
});
} else {
onAnimationComplete();
}
}
}
/**
* 统一的subPanel清理方法
* @param basePanel 主面板节点
* @param destroyPanels 是否销毁面板,默认为true
*/
private clearSubPanels(basePanel: Node, destroyPanels: boolean = true): void {
if (!basePanel || !basePanel.isValid) {
return;
}
const subPanels = this.subPanelDic.get(basePanel);
if (!subPanels || subPanels.length === 0) {
return;
}
// 清理所有子面板
for (let i = subPanels.length - 1; i >= 0; i--) {
const subPanel = subPanels[i];
if (subPanel && subPanel.isValid) {
// 清理subPanelMapping
this.subPanelMapping.delete(subPanel);
// 清理动画配置
this.subPanelAnimationConfigs.delete(subPanel);
if (destroyPanels) {
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
baseView.close();
} else {
subPanel.destroy();
}
}
}
}
// 清理subPanelDic
this.subPanelDic.delete(basePanel);
}
public openSubPanel(
panelName: string,
basePanel: Node,
@@ -330,6 +231,15 @@ export class NavigationManager {
return;
}
// 确定主面板类型
const basePanelType = this.getPanelType(basePanel);
if (!basePanelType) {
logger.error(
`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`
);
return;
}
// 使用默认动画配置
const defaultConfig: SubPanelAnimationConfig = {
openAnimation: PageTransitionType.SCALE,
@@ -339,6 +249,17 @@ export class NavigationManager {
};
const config = animationConfig || defaultConfig;
// 获取当前栈顶的子面板
const currentTopSubPanel = this.getTopSubPanel(basePanelType);
// 如果当前有栈顶子面板,先隐藏它
if (currentTopSubPanel && currentTopSubPanel.isValid) {
logger.log(
`[NavigationManager] 隐藏当前栈顶子面板: ${currentTopSubPanel.name}`
);
this.hideSubPanel(currentTopSubPanel);
}
// 如果需要隐藏basePanel,在打开动画前隐藏
if (config.hideBasePanel && basePanel.active) {
logger.log(`[NavigationManager] 隐藏basePanel: ${basePanel.name}`);
@@ -353,21 +274,18 @@ export class NavigationManager {
return;
}
if (!this.subPanelDic.has(basePanel)) {
this.subPanelDic.set(basePanel, []);
// 初始化栈(如果不存在)
if (!this.subPanelStacks.has(basePanelType)) {
this.subPanelStacks.set(basePanelType, []);
}
const subPanels = this.subPanelDic.get(basePanel);
subPanels.push(n);
// 将新子面板推入栈顶
const stack = this.subPanelStacks.get(basePanelType);
stack.push(n);
// 记录子面板到主面板的映射
this.subPanelMapping.set(n, basePanelType);
const basePanelType = this.getPanelType(basePanel);
if (basePanelType) {
this.subPanelMapping.set(n, basePanelType);
} else {
logger.warn(
`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`
);
}
// 存储动画配置
this.subPanelAnimationConfigs.set(n, config);
@@ -379,6 +297,10 @@ export class NavigationManager {
);
}
logger.log(
`[NavigationManager] 子面板已推入栈顶: ${n.name}, 当前栈深度: ${stack.length}`
);
// 执行打开动画
this.playSubPanelOpenAnimation(n, config);
};
@@ -579,6 +501,148 @@ export class NavigationManager {
// 确保清空集合
this.popupPanels.clear();
}
/**
* 获取指定主面板栈顶的子面板
* @param panelType 主面板类型
* @returns 栈顶子面板,如果栈为空则返回null
*/
private getTopSubPanel(panelType: PanelType): Node | null {
const stack = this.subPanelStacks.get(panelType);
if (!stack || stack.length === 0) {
return null;
}
return stack[stack.length - 1];
}
/**
* 清空指定主面板的子页面栈
* @param panelType 主面板类型
* @param withAnimation 是否播放动画
* @param callback 清空完成后的回调
*/
private clearSubPanelStack(
panelType: PanelType,
withAnimation: boolean = true,
callback?: Function
): void {
const stack = this.subPanelStacks.get(panelType);
if (!stack || stack.length === 0) {
callback && callback();
return;
}
if (!withAnimation) {
// 不播放动画,直接销毁所有子面板
for (let i = stack.length - 1; i >= 0; i--) {
const subPanel = stack[i];
if (subPanel && subPanel.isValid) {
// 清理映射
this.subPanelMapping.delete(subPanel);
this.subPanelAnimationConfigs.delete(subPanel);
this.hiddenBasePanels.delete(subPanel);
// 销毁节点
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
baseView.close();
} else {
subPanel.destroy();
}
}
}
this.subPanelStacks.delete(panelType);
callback && callback();
return;
}
// 播放动画
let completedCount = 0;
const totalCount = stack.length;
const onAnimationComplete = () => {
completedCount++;
if (completedCount >= totalCount) {
this.subPanelStacks.delete(panelType);
callback && callback();
}
};
// 同时播放所有子面板的关闭动画
for (let i = stack.length - 1; i >= 0; i--) {
const subPanel = stack[i];
if (subPanel && subPanel.isValid) {
// 获取动画配置
let config = this.subPanelAnimationConfigs.get(subPanel);
if (!config) {
config = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.2,
};
}
// 播放关闭动画
this.playSubPanelCloseAnimation(subPanel, config, () => {
// 清理映射
this.subPanelMapping.delete(subPanel);
this.subPanelAnimationConfigs.delete(subPanel);
this.hiddenBasePanels.delete(subPanel);
// 销毁节点
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
baseView.close();
} else {
subPanel.destroy();
}
onAnimationComplete();
});
} else {
onAnimationComplete();
}
}
}
/**
* 显示子面板
* @param panel 子面板节点
* @param config 动画配置(可选)
* @param withAnimation 是否播放打开动画,默认为 true
*/
private showSubPanel(
panel: Node,
config?: SubPanelAnimationConfig,
withAnimation: boolean = true
): void {
if (!panel || !panel.isValid) {
return;
}
panel.active = true;
if (config && withAnimation) {
this.playSubPanelOpenAnimation(panel, config);
}
}
/**
* 隐藏子面板(不销毁)
* @param panel 子面板节点
* @param callback 隐藏完成后的回调
*/
private hideSubPanel(panel: Node, callback?: Function): void {
if (!panel || !panel.isValid) {
callback && callback();
return;
}
// 直接隐藏,不播放动画(因为会被新面板覆盖)
panel.active = false;
callback && callback();
}
public closeSubPanel(
panel: Node | li_BaseView,
animationConfig?: SubPanelAnimationConfig
@@ -602,6 +666,39 @@ export class NavigationManager {
return;
}
// 确定子面板所属的主面板类型
const basePanelType = this.subPanelMapping.get(panelNode);
if (!basePanelType) {
logger.warn(
`[NavigationManager] closeSubPanel: 无法确定子面板所属的主面板类型: ${panelNode.name}`
);
return;
}
// 获取栈
const stack = this.subPanelStacks.get(basePanelType);
if (!stack || stack.length === 0) {
logger.warn(
`[NavigationManager] closeSubPanel: 栈为空或不存在,无法关闭子面板: ${panelNode.name}`
);
return;
}
// 查找子面板在栈中的索引
const panelIndex = stack.indexOf(panelNode);
if (panelIndex === -1) {
logger.warn(
`[NavigationManager] closeSubPanel: 子面板不在栈中: ${panelNode.name}`
);
return;
}
// 从栈中移除
stack.splice(panelIndex, 1);
logger.log(
`[NavigationManager] 从栈中移除子面板: ${panelNode.name}, 剩余栈深度: ${stack.length}`
);
// 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置)
let config = animationConfig;
if (!config && this.subPanelAnimationConfigs.has(panelNode)) {
@@ -616,23 +713,25 @@ export class NavigationManager {
};
}
// 检查是否需要恢复显示basePanel或显示下一层子面板
const shouldRestoreBasePanel =
config.hideBasePanel && this.hiddenBasePanels.has(panelNode);
const basePanel = shouldRestoreBasePanel
? this.hiddenBasePanels.get(panelNode)
: null;
// 获取新的栈顶子面板(如果有)
const newTopSubPanel = this.getTopSubPanel(basePanelType);
// 执行关闭动画
this.playSubPanelCloseAnimation(panelNode, config, () => {
// 如果隐藏了basePanel,需要恢复显示
// 清理映射
this.subPanelMapping.delete(panelNode);
this.subPanelAnimationConfigs.delete(panelNode);
if (this.hiddenBasePanels.has(panelNode)) {
const basePanel = this.hiddenBasePanels.get(panelNode);
if (basePanel && basePanel.isValid) {
logger.log(
`[NavigationManager] 恢复显示basePanel: ${basePanel.name}`
);
basePanel.active = true;
}
this.hiddenBasePanels.delete(panelNode);
}
// 从管理映射中移除
this.removeSubPanelFromMappings(panelNode);
// 销毁或关闭panel
try {
if (panel instanceof Node) {
@@ -643,44 +742,42 @@ export class NavigationManager {
} catch (error) {
logger.error("[NavigationManager] closeSubPanel error:", error);
}
});
}
/**
* 从管理映射中移除子面板
*/
private removeSubPanelFromMappings(panelNode: Node): void {
// 从subPanelMapping和subPanelDic中移除
if (this.subPanelMapping.has(panelNode)) {
const baseType = this.subPanelMapping.get(panelNode);
const baseNode = this.panelCache.get(baseType);
if (baseNode && this.subPanelDic.has(baseNode)) {
const subPanels = this.subPanelDic.get(baseNode);
const index = subPanels.indexOf(panelNode);
if (index !== -1) {
subPanels.splice(index, 1);
// 如果subPanels为空,删除整个条目
if (subPanels.length === 0) {
this.subPanelDic.delete(baseNode);
}
} else {
logger.warn(`[NavigationManager] ${panelNode.name} 不在子页面列表中`);
// 关闭动画完成后,决定显示什么
if (newTopSubPanel && newTopSubPanel.isValid) {
// 栈中还有子面板,显示新的栈顶(不播放动画,直接显示)
logger.log(
`[NavigationManager] 显示新的栈顶子面板(无动画): ${newTopSubPanel.name}`
);
const newTopConfig = this.subPanelAnimationConfigs.get(newTopSubPanel);
this.showSubPanel(newTopSubPanel, newTopConfig, false); // 传入 false,不播放动画
} else if (shouldRestoreBasePanel && basePanel && basePanel.isValid) {
// 栈已空,且之前隐藏了basePanel,恢复显示basePanel
logger.log(`[NavigationManager] 恢复显示basePanel: ${basePanel.name}`);
basePanel.active = true;
} else {
// 栈已空,且没有隐藏basePanel,确保basePanel可见
const cachedBasePanel = this.panelCache.get(basePanelType);
if (
cachedBasePanel &&
cachedBasePanel.isValid &&
!cachedBasePanel.active
) {
logger.log(
`[NavigationManager] 确保basePanel可见: ${cachedBasePanel.name}`
);
cachedBasePanel.active = true;
}
}
this.subPanelMapping.delete(panelNode);
}
// 移除动画配置
if (this.subPanelAnimationConfigs.has(panelNode)) {
this.subPanelAnimationConfigs.delete(panelNode);
}
// 移除隐藏映射
if (this.hiddenBasePanels.has(panelNode)) {
this.hiddenBasePanels.delete(panelNode);
}
// 如果栈完全清空,删除映射
if (stack.length === 0) {
this.subPanelStacks.delete(basePanelType);
logger.log(
`[NavigationManager] 子页面栈已清空,删除映射: ${basePanelType}`
);
}
});
}
/**
@@ -749,8 +846,8 @@ export class NavigationManager {
if (this.panelCache.has(panelType)) {
const cachedPanel = this.panelCache.get(panelType);
if (cachedPanel && cachedPanel.isValid) {
// 确保缓存panel的subPanel状态正确,清理可能残留的subPanel引用
this.clearSubPanels(cachedPanel, false); // 不销毁panel,只清理引用
// 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了
// 这里不需要再清理
// 特殊处理:主题面板刷新(避免重复调用Show)
if (panelType === PanelType.THEME) {
@@ -796,8 +893,8 @@ export class NavigationManager {
return;
}
// 同时开始子面板的关闭动画
this.clearSubPanelsWithAnimation(panel);
// 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了
// 这里只需要播放主面板的隐藏动画
if (!panel.active) panel.active = true;