工作分享1(26.5.27):基于栈实现全局返回逻辑通用架构设计(适配异步 + 确认弹窗)

一、前言

做 UI 开发时,多级页面跳转、返回是几乎所有客户端项目都会遇到的刚需。

不管是移动端 APP、游戏 UI、桌面客户端,都会出现这类痛点

页面层级多,每个页面单独写返回逻辑,代码冗余、维护麻烦;

部分页面退出前需要二次确认(比如未保存内容、退出当前流程);

页面关闭伴随动画、网络请求等异步操作,普通同步返回逻辑容易错乱;

本文基于栈 (Stack) 先进后出的特性,设计一套通用全局返回架构,可套用在 Unity 大部分 UI 项目中。方案经过实战落地,同时也是面试中非常加分的 UI 架构实战案例。

二、设计思路与应用场景

  1. 场景举例
    拿日常使用的手机 APP 举例:
    场景 1:微信 → 通讯录 → 好友资料页 → 聊天页,连续多级页面,点击返回逐层退回;
    场景 2:淘宝 APP → 商品列表 → 商品详情 → 下单页,退出下单页需弹窗提示「是否放弃订单」;
    场景 3:抖音 APP,页面关闭伴随渐变、位移动画,返回需要等待动画执行完毕再销毁界面。
    以上场景,用一套栈结构统一管理,就能彻底告别每个页面手写返回逻辑。
  2. 架构目标
    统一接管所有页面的返回行为,代码复用;
    同时支持同步操作、异步动画 / 请求、退出确认弹窗三大能力;
    自动管理页面层级,保证返回顺序和打开顺序完全对应;
    低侵入,原有 UI 框架无需大幅改造。

三、核心数据结构定义

首先定义数据载体类,用来存储每一个页面对应的返回行为,区分同步、异步、确认校验三类逻辑:

csharp 复制代码
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;

/// <summary>
/// 页面返回行为数据
/// 承载单个页面所有返回相关逻辑
/// </summary>
public class PageBackInfo
{
    // 页面标识(日志、调试使用)
    public string PageName;
    // 同步返回逻辑(关闭页面、刷新数据等)
    public Action SyncBackAction;
    // 异步返回逻辑(播放动画、网络请求、延时关闭等)
    public Func<UniTask> AsyncBackAction;
    // 退出确认逻辑(弹窗二次确认,返回true允许退出,false拦截返回)
    public Func<UniTask<bool>> CheckConfirmAction;

    public PageBackInfo(string pageName, Action syncAction, 
        Func<UniTask> asyncAction = null, Func<UniTask<bool>> confirmAction = null)
    {
        PageName = pageName;
        SyncBackAction = syncAction;
        AsyncBackAction = asyncAction;
        CheckConfirmAction = confirmAction;
    }
}

全局返回管理器中,使用Stack存储页面层级,栈天然适配「先打开、后返回」的交互逻辑:

csharp 复制代码
/// <summary>
/// 全局返回管理器(单例,全局唯一)
/// </summary>
public class GlobalBackManager
{
    public static GlobalBackManager Instance { get; private set; }
    // 页面层级栈
    private Stack<PageBackInfo> _pageStack = new Stack<PageBackInfo>();

    private void Awake()
    {
        // 单例防重复创建
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        // 跨场景保留管理器
        DontDestroyOnLoad(gameObject);
    }
}

四、核心功能实现

  1. 页面入栈:注册返回行为
    每当打开一个新页面,就将该页面的返回信息压入栈。这是使用该架构的唯一接入点。
csharp 复制代码
/// <summary>
/// 注册页面(页面打开时调用,入栈)
/// </summary>
public void RegisterPage(PageBackInfo pageInfo)
{
    if (pageInfo == null) return;
    _pageStack.Push(pageInfo);
    UnityEngine.Debug.Log($"页面入栈:{pageInfo.PageName} | 当前层级:{_pageStack.Count}");
}
  1. 核心返回逻辑
    点击返回按钮 / 手机返回键时,执行统一返回流程:先校验确认弹窗 → 执行同步逻辑 → 执行异步逻辑 → 页面出栈。
csharp 复制代码
/// <summary>
/// 全局统一返回入口
/// </summary>
public async void DoBack()
{
    // 栈为空,已是根页面,无页面可返回
    if (_pageStack.Count == 0)
    {
        UnityEngine.Debug.Log("已到达首页,无法继续返回");
        return;
    }

    // 取出栈顶页面(当前最顶层页面)
    var currentPage = _pageStack.Peek();

    // 第一步:执行二次确认校验
    if (currentPage.CheckConfirmAction != null)
    {
        bool canBack = await currentPage.CheckConfirmAction.Invoke();
        // 用户选择取消,直接终止返回流程
        if (!canBack) return;
    }

    // 第二步:执行同步返回逻辑
    currentPage.SyncBackAction?.Invoke();

    // 第三步:执行异步返回逻辑(动画/延时/网络)
    if (currentPage.AsyncBackAction != null)
    {
        await currentPage.AsyncBackAction.Invoke();
    }

    // 第四步:逻辑执行完毕,页面出栈
    _pageStack.Pop();
    UnityEngine.Debug.Log($"页面出栈:{currentPage.PageName} | 当前层级:{_pageStack.Count}");
}
  1. 其他:清空栈(场景切换 / 回到首页)
    切换主界面、退出功能模块时,清空栈,避免脏数据残留:
csharp 复制代码
/// <summary>
/// 清空所有页面层级(回到根页面)
/// </summary>
public void ClearAllPage()
{
    _pageStack.Clear();
    UnityEngine.Debug.Log("已清空页面返回栈");
}

五、通用业务场景实战演示

下面用大家日常都在用的 APP 交互做演示,覆盖普通页面、确认弹窗、异步动画三大典型场景。

场景 1:多级普通页面(无弹窗、无动画)

模拟流程:首页 → 消息列表页 → 聊天详情页

打开消息列表页,注册返回逻辑:关闭当前页面,回到首页

csharp 复制代码
// 打开消息列表时调用
var msgPageInfo = new PageBackInfo(
    pageName: "消息列表页",
    syncAction: () =>
    {
        // 同步逻辑:关闭消息列表界面
        UIManager.CloseUI("MsgListPanel");
    }
);
GlobalBackManager.Instance.RegisterPage(msgPageInfo);

打开聊天详情页,注册返回逻辑:关闭聊天页,回到消息列表

csharp 复制代码
// 打开聊天页时调用
var chatPageInfo = new PageBackInfo(
    pageName: "聊天详情页",
    syncAction: () =>
    {
        UIManager.CloseUI("ChatPanel");
    }
);
GlobalBackManager.Instance.RegisterPage(chatPageInfo);

返回流程:

点击返回 → 聊天详情页出栈 → 回到消息列表;再次点击返回 → 消息列表出栈 → 回到首页,逻辑完全符合预期。

场景 2:带二次确认的页面(电商下单页)

模拟流程:商城首页 → 商品详情 → 下单结算页

需求:在下单页点击返回,弹出提示「订单尚未提交,确定要退出吗?」,取消则留在当前页,确认则关闭页面。

csharp 复制代码
// 打开下单结算页时注册
var orderPageInfo = new PageBackInfo(
    pageName: "下单结算页",
    syncAction: () =>
    {
        UIManager.CloseUI("OrderPanel");
    },
    asyncAction: null,
    confirmAction: async () =>
    {
        // 弹出通用确认弹窗,等待用户选择
        bool userSelect = await CommonTipPanel.ShowConfirm("订单未提交,是否退出?");
        return userSelect;
    }
);
GlobalBackManager.Instance.RegisterPage(orderPageInfo);

返回流程:

点击返回 → 触发确认弹窗 → 选「取消」:终止返回;选「确定」:关闭页面、执行出栈。

场景 3:带异步动画的页面(短视频播放页)

模拟流程:推荐列表 → 短视频播放页

需求:页面关闭需要播放 1 秒淡出动画,必须等待动画结束后,再销毁界面。

csharp 复制代码
// 打开短视频播放页时注册
var videoPageInfo = new PageBackInfo(
    pageName: "短视频播放页",
    syncAction: null,
    asyncAction: async () =>
    {
        // 执行淡出动画,等待动画播放完成
        await VideoPlayerPanel.PlayCloseAnimation();
        // 动画结束后关闭界面
        UIManager.CloseUI("VideoPlayerPanel");
    }
);
GlobalBackManager.Instance.RegisterPage(videoPageInfo);

返回流程:

点击返回 → 执行淡出动画(异步等待)→ 动画结束 → 关闭页面、出栈。

六、方案优缺点 & 踩坑总结

  1. 优势
    通用性极强:一套代码适配所有 UI 页面,游戏、APP、客户端都能用;
    解耦性高:页面不用互相依赖,只需要在打开时注册自身返回逻辑;
    能力全面:同时支持同步、异步、二次确认,覆盖 90% 以上 UI 返回场景;
    层级稳定:依托栈结构,严格保证页面跳转 / 返回顺序,不会出现层级错乱。
  2. 注意事项(避坑重点,面试须知)
    1. 必须成对入栈、出栈
      页面正常打开要RegisterPage,禁止绕过管理器直接销毁界面,否则栈会残留脏数据,后续返回逻辑异常。
    2. 场景切换务必清空栈
      切换主模块、跳转场景时,调用ClearAllPage(),防止旧页面数据干扰新流程。
    3. 异步逻辑做好异常捕获
      动画、网络请求等异步操作建议增加try-catch,避免异常打断整个返回流程。
    4. 根页面不注册栈数据
      首页 / 主面板作为根节点,不需要入栈,保证点击返回时不会无限循环。

七、拓展优化方向(面试加分项)

  1. 支持一键返回首页
    封装方法循环出栈,直接清空所有层级,快速回到根页面;
  2. 适配移动端物理返回键
    在 Unity 中监听手机物理返回键,直接调用DoBack(),和原生 APP 体验一致;
  3. 编辑器调试工具
    开发 Editor 调试面板,实时查看栈内所有页面名称、层级数量,快速排查 UI 层级问题;
  4. 栈数据缓存优化
    针对需要常驻的页面,增加标记,支持「返回不销毁页面」的需求。

八、写在最后

这套基于栈的返回架构,是 UI 开发中非常经典且实用的设计。原理简单、落地成本低,同时能体现数据结构结合业务落地的能力。

日常开发中不用重复编写大量返回逻辑,项目可维护性大幅提升。如果大家在 Unity UI、客户端开发中遇到多级页面管理问题,都可以参考这套思路。

欢迎评论区交流不同项目的 UI 返回实现方案!

相关推荐
z落落1 小时前
C# 多态 + 函数重载(静态多态)+运算符重载
开发语言·c#
Fms_Sa2 小时前
分治法—最大子段问题
算法·c#
rick9772 小时前
C# 动态对象实战:用 DynamicObject 打造你的"万能插件架构"
c#
上海云盾第一敬业销售2 小时前
游戏盾架构解析:保障在线游戏的安全
安全·游戏·架构
玩c#的小杜同学2 小时前
未来 AI 会装进电脑里吗?本地 AI、AI PC 和企业隐私计算
人工智能·微软·c#·电脑·英伟达
wgc2k2 小时前
Oops Framework-1-学习路线(Cocos Creator + ECS)
游戏·cocos2d
荔枝吻2 小时前
【保姆级喂饭教程】Inno Setup下载安装、添加中文、打包、自动化教程
c#·vs·inno setup
蜗牛~turbo2 小时前
金蝶云星空 二开得到来源单单据体2数据包
windows·c#·金蝶·dynamicobject
雪豹阿伟3 小时前
14.C# —— 静态成员、只读常量、继承、访问修饰符、多态、抽象类
c#·上位机