【设计和开发一套简单自己主动化UI框架】
!有兴趣的朋友请直接移步Github,本帖子已经不做更新,框架的详细的实现已经做了优化和代码整理,本文仅仅介绍了详细的设计思路!
目标:编写一个简单通用UI框架用于管理页面和完毕导航跳转
框架详细实现的功能和需求
- 载入。显示,隐藏,关闭页面,依据标示获得对应界面实例
- 提供界面显示隐藏动画接口
- 单独界面层级。Collider。背景管理
- 依据存储的导航信息完毕界面导航
- 界面通用对话框管理(多类型Message Box)
- 便于进行需求和功能扩展(比方,在跳出页面之前加入逻辑处理等)
编写UI框架意义
- 打开,关闭,层级,页面跳转等管理问题集中化,将外部切换等逻辑交给UIManager处理
- 功能逻辑分散化,每一个页面维护自身逻辑,依托于框架便于多人协同开发,不用关心跳转和显示关闭细节
- 通用性框架可以做到简单的代码复用和"项目经验"沉淀
步入正题,怎样实现
- 窗体类设计:基本窗体对象,维护自身逻辑维护
- 窗体管理类:控制被管理窗体的打开和关闭等逻辑(详细设计请看下文)
- 动画接口:提供打开和关闭动画接口,提供动画完毕回调函数等
- 层级,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数据
- 返回操作(主动关闭当前界面或者点击返回button):从堆栈中Pop出一个界面状态,将对应的界面又一次打开
- 怎么衔接:比方从一个界面没有回到上一个状态而是直接的跳转到其它的界面,这个时候须要将BackSequence清空由于当前的导航链已经被破坏。当BackSequence为空须要依据当前窗体指定的PreWindowId告知系统当从该界面返回,须要到达的指定页面。这样就能解决怎么衔接的问题,假设没断,继续运行导航,否则清空数据,依据PreWindowId进行导航
- 设置三个经常使用层级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;
}
兴许须要改进和增强计划
- 图集管理,针对大中型游戏对游戏内存要求苛刻的项目,一般都会对UI图集贴图资源进行动态管理,载入和卸载图集,保证UI贴图占用较少内存
- 添加一些通用处理:变灰操作,Mask遮罩(一般用于入门教程中)等
- 在进行切换的过程能够须要Load新场景需求,尽管这个也能够在UI框架外实现
- 对话系统也算是UI框架的功能,新手引导系统也能够添加到UI框架中,统一管理和处理新手引导逻辑
实现效果
整个框架的核心部分介绍完成,有须要查看源代码的请移步GitHub。兴许会继续完好和整理,希望可以给耐心看到结尾的朋友一点启示或者带来一点帮助。存在错误和改进的地方也希望留言交流共同进步学习~
有些时候,我们总是知道这么个理明确该如何实现。可是关键的就是要动手实现出来,实现的过程会发现自己的想法在慢慢优化。不断的需求和bug的产生让框架慢慢成熟,能够投入项目使用提升一些开发效率和降低工作量。
【设计和开发一套简单自己主动化UI框架】的更多相关文章
- 设计与开发一款简单易用的Web报表工具(支持常用关系数据及hadoop、hbase等)
EasyReport是一个简单易用的Web报表工具(支持Hadoop,HBase及各种关系型数据库),它的主要功能是把SQL语句查询出的行列结构转换成HTML表格(Table),并支持表格的跨行(Ro ...
- 设计系统(Design System),设计和开发之间的“DevOps”
最近,我们网站的上新增了几个新功能,比如通过导航栏的QR Code可以下载App:通过Carousel的方式,显示多条信息. 以往这样的功能可能需要2-3个Sprints完成,但是现在这些功能都是在一 ...
- 游戏UI框架设计(五): 配置管理与应用
游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所 ...
- 自己动手设计并实现一个linux嵌入式UI框架
一直以来都是使用现成的UI框架,如微软的window.QT等,因为它有各种控件(如button.window.edit等)都已经封装实现好了.我们只要拿来用就是了,也一直认为它很神圣,没有深入了解它背 ...
- TERSUS无代码开发(笔记09)-简单实例前端样式设计
前端常用样式设计 =========================================================================================== ...
- TERSUS无代码开发(笔记06)-简单实例手机端页面设计
手机端的设计 1.页面说明 2.默认页面===>提交请假单(上面页面双击进入,页面主要编辑区) 2.1默认页面===>提交请假单===>头部区(页面部份主要编辑区01) 2.1.1默 ...
- TERSUS无代码开发(笔记05)-简单实例电脑端页面设计
案例笔记电脑端页面设计 1.新建项目(请假管理qjgl) 2.开发软件界面介绍(常用的功能按键) 3.目录中显示元件对象 4.对元件对象的操作主要方式是双击(双击哪个元件, ...
- jQuery2.0应用开发:SSH框架整合jQuery2.0实战OA办公自己主动化(VSS、operamasks-UI框架)
我的qq是2059055336,对这个课程有兴趣的能够加我qq联系. 一.本课程是怎么样的一门课程(全面介绍) 1.1.课程的背景 jQuery 2.0 正式版公布.不在支持 IE 6/7/8 ...
- Atitit.uml2 api 的编程代码实现设计uml开发 使用eclipse jar java 版本
Atitit.uml2 api 的编程代码实现设计uml开发 使用eclipse jar java 版本 1. clipse提供了UML的底层Java包, 1 2. MDTUML2Getting St ...
随机推荐
- mysql_secure_installation
安装完mysql-server 会提示可以运行mysql_secure_installation.运行mysql_secure_installation会执行几个设置: a)为root用户设置密码 ...
- 【linux排错】"error while loading shared libraries: xxx.so.x" 错误的原因和解决办法
一般我们在Linux下执行某些外部程序的时候可能会提示找不到共享库的错误, 比如: lcw: error : cannot open shared object file: No such file ...
- 【ARM】定时器
PWM定时器 PWN:脉冲宽度调制 每个定时器都有一个专用的由定时器时钟驱动的16位递减计数器.当递减计数器的计数值达到0的时候,就会产生定时中断请求来通知CPU定时器操作完成.当定时器递减计数器达到 ...
- scala连接数据库
scala连接数据库 使用JDBC即可: 在sbt中添加对应依赖 libraryDependencies ++= Seq( "mysql" % "mysql-connec ...
- SAP BI vs. Oracle BI
对比Oracle BI产品和SAP BI 产品,做一些简单的产品功能比较,经供参考. 这里把SAP和Oracle同类的产品放在一行,用于比较. SAP BI 特点 Oracle BI 特点 BW 和S ...
- 我的Linux学习之路及参考书籍
学习目的 很简单的考虑,最近在各大招聘网站上找工作,发现多数c/c++开发职位都需要Linux开发经验,让我很苦恼,因为Linux我到目前为止知之甚少,知道Linux的概念,也在大学期间了解过一段时间 ...
- scala工程导入报错:scalatest_2.10-1.9.1.jar is cross-compiled with an incompatible version of Scala (2.10).
错误原因: The Scala IDE tries to check if binary incompatible Scala libraries have been inadvertently mi ...
- 关于OpenVR
一直在期待一种大一统的开放的VR技术规范,虽然短期内这点明显是不太现实的.前几天在翻译Godot的开发进展#6那篇文章时,看到了一个词OpenVR,瞬间有感觉了. 从我的经历的技术规范演进版本来看,从 ...
- 私有IP地址共有三个范围段
在现在的网络中,IP地址分为公网IP和私有IP地址.公网IP是在Internet使用的IP地址,而私有IP地址是在局域网中使用的IP地址. 由于我们目前使用的IP V4协议的限制,现在IP地址的数量是 ...
- [sed] linux sed 批量替换字符串
比如,要将目录/modules下面所有文件中的zhangsan都修改成lisi,这样做: sed -i "s/zhangsan/lisi/g" `grep zhangsan -rl ...