【设计和开发一套简单自己主动化UI框架】

!有兴趣的朋友请直接移步Github,本帖子已经不做更新,框架的详细的实现已经做了优化和代码整理,本文仅仅介绍了详细的设计思路!

目标:编写一个简单通用UI框架用于管理页面和完毕导航跳转

终于的实现效果请拉到最下方查看

框架详细实现的功能和需求

  • 载入。显示,隐藏,关闭页面,依据标示获得对应界面实例
  • 提供界面显示隐藏动画接口
  • 单独界面层级。Collider。背景管理
  • 依据存储的导航信息完毕界面导航
  • 界面通用对话框管理(多类型Message Box)
  • 便于进行需求和功能扩展(比方,在跳出页面之前加入逻辑处理等)

编写UI框架意义

  • 打开,关闭,层级,页面跳转等管理问题集中化,将外部切换等逻辑交给UIManager处理
  • 功能逻辑分散化,每一个页面维护自身逻辑,依托于框架便于多人协同开发,不用关心跳转和显示关闭细节
  • 通用性框架可以做到简单的代码复用和"项目经验"沉淀

步入正题,怎样实现

  1. 窗体类设计:基本窗体对象,维护自身逻辑维护
  2. 窗体管理类:控制被管理窗体的打开和关闭等逻辑(详细设计请看下文)
  3. 动画接口:提供打开和关闭动画接口,提供动画完毕回调函数等
  4. 层级,Collider背景管理

窗体基类设计

框架中设计的窗体类型和框架所需定义例如以下

    public enum UIWindowType
    {
        Normal,    // 可推出界面(UIMainMenu,UIRank等)
        Fixed,     // 固定窗体(UITopBar等)
        PopUp,     // 模式窗体
    }

    public enum UIWindowShowMode
    {
        DoNothing,
        HideOther,     // 闭其它界面
        NeedBack,      // 点击返回button关闭当前,不关闭其它界面(须要调整好层级关系)
        NoNeedBack,    // 关闭TopBar,关闭其它界面,不增加backSequence队列
    }

    public enum UIWindowColliderMode
    {
        None,      // 显示该界面不包括碰撞背景
        Normal,    // 碰撞透明背景
        WithBg,    // 碰撞非透明背景
    }
using UnityEngine;
using System.Collections;
using System;

namespace CoolGame
{
    /// <summary>
    /// 窗体基类
    /// </summary>
    public class UIBaseWindow : MonoBehaviour
    {
        protected UIPanel originPanel;

        // 假设须要能够加入一个BoxCollider屏蔽事件
        private bool isLock = false;
        protected bool isShown = false;

        // 当前界面ID
        protected WindowID windowID = WindowID.WindowID_Invaild;

        // 指向上一级界面ID(BackSequence无内容,返回上一级)
        protected WindowID preWindowID = WindowID.WindowID_Invaild;
        public WindowData windowData = new WindowData();

        // Return处理逻辑
        private event BoolDelegate returnPreLogic = null;

        protected Transform mTrs;
        protected virtual void Awake()
        {
            this.gameObject.SetActive(true);
            mTrs = this.gameObject.transform;
            InitWindowOnAwake();
        }

        private int minDepth = 1;
        public int MinDepth
        {
            get { return minDepth; }
            set { minDepth = value; }
        }

        /// <summary>
        /// 是否能加入到导航数据中
        /// </summary>
        public bool CanAddedToBackSeq
        {
            get
            {
                if (this.windowData.windowType == UIWindowType.PopUp)
                    return false;
                if (this.windowData.windowType == UIWindowType.Fixed)
                    return false;
                if (this.windowData.showMode == UIWindowShowMode.NoNeedBack)
                    return false;
                return true;
            }
        }

        /// <summary>
        /// 界面是否要刷新BackSequence数据
        /// 1.显示NoNeedBack或者从NoNeedBack显示新界面 不更新BackSequenceData(隐藏自身就可以)
        /// 2.HideOther
        /// 3.NeedBack
        /// </summary>
        public bool RefreshBackSeqData
        {
            get
            {
                if (this.windowData.showMode == UIWindowShowMode.HideOther
                    || this.windowData.showMode == UIWindowShowMode.NeedBack)
                    return true;
                return false;
            }
        }

        /// <summary>
        /// 在Awake中调用。初始化界面(给界面元素赋值操作)
        /// </summary>
        public virtual void InitWindowOnAwake()
        {
        }

        /// <summary>
        /// 获得该窗体管理类
        /// </summary>
        public UIManagerBase GetWindowManager
        {
            get
            {
                UIManagerBase baseManager = this.gameObject.GetComponent<UIManagerBase>();
                return baseManager;
            }
            private set { }
        }

        /// <summary>
        /// 重置窗体
        /// </summary>
        public virtual void ResetWindow()
        {
        }

        /// <summary>
        /// 初始化窗体数据
        /// </summary>
        public virtual void InitWindowData()
        {
            if (windowData == null)
                windowData = new WindowData();
        }

        public virtual void ShowWindow()
        {
            isShown = true;
            NGUITools.SetActive(this.gameObject, true);
        }

        public virtual void HideWindow(Action action = null)
        {
            IsLock = true;
            isShown = false;
            NGUITools.SetActive(this.gameObject, false);
            if (action != null)
                action();
        }

        public void HideWindowDirectly()
        {
            IsLock = true;
            isShown = false;
            NGUITools.SetActive(this.gameObject, false);
        }

        public virtual void DestroyWindow()
        {
            BeforeDestroyWindow();
            GameObject.Destroy(this.gameObject);
        }

        protected virtual void BeforeDestroyWindow()
        {
        }

        /// <summary>
        /// 界面在退出或者用户点击返回之前都能够注冊运行逻辑
        /// </summary>
        protected void RegisterReturnLogic(BoolDelegate newLogic)
        {
            returnPreLogic = newLogic;
        }

        public bool ExecuteReturnLogic()
        {
            if (returnPreLogic == null)
                return false;
            else
                return returnPreLogic();
        }
    }
}

动画接口设计

界面能够继承该接口进行实现打开和关闭动画

    /// <summary>
    /// 窗体动画
    /// </summary>
    interface IWindowAnimation
    {
        /// <summary>
        /// 显示动画
        /// </summary>
        void EnterAnimation(EventDelegate.Callback onComplete);

        /// <summary>
        /// 隐藏动画
        /// </summary>
        void QuitAnimation(EventDelegate.Callback onComplete);

        /// <summary>
        /// 重置动画
        /// </summary>
        void ResetAnimation();
    }
        public void EnterAnimation(EventDelegate.Callback onComplete)
        {
            if (twAlpha != null)
            {
                twAlpha.PlayForward();
                EventDelegate.Set(twAlpha.onFinished, onComplete);
            }
        }

        public void QuitAnimation(EventDelegate.Callback onComplete)
        {
            if (twAlpha != null)
            {
                twAlpha.PlayReverse();
                EventDelegate.Set(twAlpha.onFinished, onComplete);
            }
        }

        public override void ResetWindow()
        {
            base.ResetWindow();
            ResetAnimation();
        }

窗体管理和导航设计实现

导航功能实现通过一个显示窗体堆栈实现。每次打开和关闭窗体通过推断窗体属性和类型更新处理BackSequence数据

  • 打开界面:将当前界面状态压入堆栈中更新BackSequence数据
  • 返回操作(主动关闭当前界面或者点击返回button):从堆栈中Pop出一个界面状态,将对应的界面又一次打开
  • 怎么衔接:比方从一个界面没有回到上一个状态而是直接的跳转到其它的界面,这个时候须要将BackSequence清空由于当前的导航链已经被破坏。当BackSequence为空须要依据当前窗体指定的PreWindowId告知系统当从该界面返回,须要到达的指定页面。这样就能解决怎么衔接的问题,假设没断,继续运行导航,否则清空数据,依据PreWindowId进行导航

导航系统中关键性设计:

游戏中能够存在多个的Manager进行管理(一般在非常少需求下才会使用),每一个管理对象须要维护自己的导航信息BackSequence。每次退出一个界面须要检測当前退出的界面是否存在对应的Manager管理,假设存在则须要先运行Manager退出操作(退出过程分步进行)保证界面一层接着一层正确退出

窗体层级,Collider。统一背景加入怎样实现?

有非常多方式进行层级管理,该框架选择的方法例如以下

  • 设置三个经常使用层级Root,依据窗体类型在载入到游戏中时加入到相应的层级Root以下就可以。每次加入又一次计算设置层级(通过UIPanel的depth实现)保证每次打开一个新窗体层级显示正确,每次窗体内通过depth的大小区分层级关系
  • 依据窗体Collider和背景类型,在窗体的最小Panel上面加入Collider或者带有碰撞体的BackGround就可以

详细实现例如以下:

private void AdjustBaseWindowDepth(UIBaseWindow baseWindow)
{
    UIWindowType windowType = baseWindow.windowData.windowType;
    int needDepth = 1;
    if (windowType == UIWindowType.Normal)
    {
        needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UINormalWindowRoot.gameObject, false) + 1, normalWindowDepth, int.MaxValue);
        Debug.Log("[UIWindowType.Normal] maxDepth is " + needDepth + baseWindow.GetID);
    }
    else if (windowType == UIWindowType.PopUp)
    {
        needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIPopUpWindowRoot.gameObject) + 1, popUpWindowDepth, int.MaxValue);
        Debug.Log("[UIWindowType.PopUp] maxDepth is " + needDepth);
    }
    else if (windowType == UIWindowType.Fixed)
    {
        needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIFixedWidowRoot.gameObject) + 1, fixedWindowDepth, int.MaxValue);
        Debug.Log("[UIWindowType.Fixed] max depth is " + needDepth);
    }
    if(baseWindow.MinDepth != needDepth)
        GameUtility.SetTargetMinPanel(baseWindow.gameObject, needDepth);
    baseWindow.MinDepth = needDepth;
}

/// <summary>
/// 窗体背景碰撞体处理
/// </summary>
private void AddColliderBgForWindow(UIBaseWindow baseWindow)
{
    UIWindowColliderMode colliderMode = baseWindow.windowData.colliderMode;
    if (colliderMode == UIWindowColliderMode.None)
        return;

    if (colliderMode == UIWindowColliderMode.Normal)
        GameUtility.AddColliderBgToTarget(baseWindow.gameObject, "Mask02", maskAtlas, true);
    if (colliderMode == UIWindowColliderMode.WithBg)
        GameUtility.AddColliderBgToTarget(baseWindow.gameObject, "Mask02", maskAtlas, false);
}

多形态MessageBox实现

这个应该是项目中一定会用到的功能,说下该框架简单的实现

  • 三个button三种回调逻辑:左中右三个button,提供设置内容,设置回调函数的接口就可以
  • 提供接口设置核心Content
  • 不同作用下不同的button不会隐藏和显示
public void SetCenterBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
{
    lbCenter.text = msg;
    NGUITools.SetActive(btnCenter, true);
    UIEventListener.Get(btnCenter).onClick = callBack;
}

public void SetLeftBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
{
    lbLeft.text = msg;
    NGUITools.SetActive(btnLeft, true);
    UIEventListener.Get(btnLeft).onClick = callBack;
}

public void SetRightBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
{
    lbRight.text = msg;
    NGUITools.SetActive(btnRight, true);
    UIEventListener.Get(btnRight).onClick = callBack;
}

兴许须要改进和增强计划

  1. 图集管理,针对大中型游戏对游戏内存要求苛刻的项目,一般都会对UI图集贴图资源进行动态管理,载入和卸载图集,保证UI贴图占用较少内存
  2. 添加一些通用处理:变灰操作,Mask遮罩(一般用于入门教程中)等
  3. 在进行切换的过程能够须要Load新场景需求,尽管这个也能够在UI框架外实现
  4. 对话系统也算是UI框架的功能,新手引导系统也能够添加到UI框架中,统一管理和处理新手引导逻辑

需求总是驱动着系统逐渐强大,逐渐完好,逐渐发展,一步一步来吧~

实现效果

整个框架的核心部分介绍完成,有须要查看源代码的请移步GitHub。兴许会继续完好和整理,希望可以给耐心看到结尾的朋友一点启示或者带来一点帮助。存在错误和改进的地方也希望留言交流共同进步学习~

有些时候,我们总是知道这么个理明确该如何实现。可是关键的就是要动手实现出来,实现的过程会发现自己的想法在慢慢优化。不断的需求和bug的产生让框架慢慢成熟,能够投入项目使用提升一些开发效率和降低工作量。

时间: 2025-01-17 18:58:55

【设计和开发一套简单自己主动化UI框架】的相关文章

设计与开发一款简单易用的Web报表工具(支持常用关系数据及hadoop、hbase等)

EasyReport是一个简单易用的Web报表工具(支持Hadoop,HBase及各种关系型数据库),它的主要功能是把SQL语句查询出的行列结构转换成HTML表格(Table),并支持表格的跨行(RowSpan)与跨列(ColSpan).同时它还支持报表Excel导出.图表显示及固定表头与左边列的功能.总体架构如下图所示: 目录 开发环境(Development Environment) 安装与部署(Installation & Deployment) 从源代码安装(From Source Co

jQuery框架开发一个最简单的幻灯效果

在线演示 在这个课程中,我们将介绍如何使用jQuery来开发一个最简单的图片幻灯效果. 立刻观看互动课程:jQuery框架开发一个最简单的幻灯效果 阅读原文:jQuery框架开发一个最简单的幻灯效果 jQuery框架开发一个最简单的幻灯效果

自己动手设计并实现一个linux嵌入式UI框架

一直以来都是使用现成的UI框架,如微软的window.QT等,因为它有各种控件(如button.window.edit等)都已经封装实现好了.我们只要拿来用就是了,也一直认为它很神圣,没有深入了解它背后是如何实现的,近段时间有做这方面的项目,并且由我设计并实现的,说实在的,当时只是想找个简单易用的UI框架,QT.minigui....都被我给否了,因为团队成员都不熟,包括我,考虑到开发过程中会遇到问题不好解决,用别人写的框架就是会有这样的顾虑,一般很难领悟那么深,另外加上开发团队成员水平不一,以

游戏UI框架设计(五): 配置管理与应用

游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所谓"配置管理"是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替"硬编码"方式.      这里笔者就对游戏产品中大量应用到动态加载的情形,开发出一套通用的配置管理(脚本)工具.该工具可以很方便的对于具备"键值对&

游戏UI框架设计(五): 配置管理与应用

游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所谓"配置管理"是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替"硬编码"方式. 这里笔者就对游戏产品中大量应用到动态加载的情形,开发出一套通用的配置管理(脚本)工具.该工具可以很方便的对于具备"键值对"

基于Bootstrap框架的临床数据管理系统的设计与开发

    基于Bootstrap框架的临床数据管理系统的设计与开发     2018年11月10日 目  录 第一章绪论... 6 1.1 选题背景及其意义... 6 1.2国内外研究现状... 7 1.2.1 临床大数据管理系统发展现状... 7 1.2.2医疗电子表单管理发展现状... 8 1.3研究目标... 9 1.4 研究内容... 10 1.5论文整体结构... 10 第二章相关技术研究... 12 2.1 AngularJS技术简述... 12 2.2 RESTful API +sw

组件化设计与开发

http://colachan.com/post/3545 终于迎来一期特刊.最近打算在公司内部做一个分享,讲的是组件化的设计与开发的思维方式.准备完演讲资料,发现这完全可以改成一篇文章.藏着掖着不合适,发出来分享给有需求的朋友吧,就当是个试讲了,希望大家帮忙指出错误. 下载地址:https://www.jianguoyun.com/p/DY1Z3bEQwKOaBhimoyg 由于本文首先是以keynote的形式诞生的,其中还有动画和视频,所以我比较推荐大家直接下载keynote文件(也存了PP

如何快速开发一套微信商城小程序?

小程序的价值相信已经不用我多说,未来大部分应用场景都将使用微信小程序进行研发.开发一套商城小程序需要哪些步骤,怎么开通?快搞定小编来为大家解疑. 第一步:确定商城小程序产品功能.UI风格 在设计小程序的时候一定要符合"轻便.即用即走"的定位,小程序只是场景化的产品,功能不宜过多,更多的是起到平台覆盖和完善用户使用场景的作用. 第二步:注册微信小程序并申请微信支付 进入微信公众平台mp.weixin.qq.com,按提示注册即可.需注意的是,个人暂时不能注册小程序,注册时必须提供企业营业

Hello,Cardboard!!-如何开发一个最简单的Cardboard虚拟现实应用(一)

温馨提醒,本篇为介绍篇,如果只想看如何开发的具体步骤请参看<Hello,Cardboard!!-如何开发一个最简单的Cardboard虚拟现实应用(三)> 前述:恕我啰嗦一下,主要照顾对cardboard不太了解的朋在,Cardboard是由Google公司的两位巴黎办公室的员工利用业余时间创作出来的作品,它最大的特点就是将原来人们以为高大上的虚拟现实技术以廉价的方式带进了公众的视野,到目前为止,google已推出了改良版的cardboard 2代盒子,相比1代,2代改善了成像,增加了视野范围