• unity游戏框架学习-UI模块


    框架概述

    1.界面的加载、卸载

    2.打开、关闭、隐藏、显示界面,这边隐藏是指界面被遮挡的意思,一般来说,界面被遮住时,应该关闭界面的更新

    3.界面栈的管理,主要是用于场景切换时需要回到上一个场景打开的界面栈

    4.需要的功能:图片镜像(节省资源)、滑动列表(复用)、模糊背景等

    注意点:

    1.界面的生成:class的生成、预制体的实例化,类和实例的关联。

    业务打开一个界面需要传入界面的标识(枚举、或者字符串),如何通过这个标识找到预制体并实例化go、如何生成指定的界面view,如何绑定view和go

    如何销毁一个界面,清除ab缓存、清除引用关系、destory go

    2.界面的层级关系:每次打开一个新的UI,都将它堆入栈,关闭时出栈。这个栈是一个特殊的栈,例如它可以实现,某个不在栈顶的UI,可以“TOP”到栈顶。

    打开一个界面:1.从已打开界面搜索,避免重复打开界面。2.从缓存界面搜索,避免重复加载。3.隐藏栈顶界面。4.打开新界面

    关闭一个界面:1.关闭界面并加入缓存。2.从已打开界面栈顶取出一个界面,显示该界面。3.重复步骤2直到打开一个全屏界面。

    退出场景:1.所有界面入栈,当再次回到场景时可以恢复界面栈。2.关闭界面

    进入场景:1.如果需要恢复界面栈,从界面栈取出界面并打开显示该界面。2.重复1步骤直到打开一个全屏界面。3.不需要恢复:打开当前场景的界面。

    3.界面间的通信:最好不要有界面之间的通信,界面的更新通过数据(逻辑)类发送消息通知给界面。例如使用道具后界面的更新,数据类接收到服务器道具使用成功后,发送道具更新消息BAG_DATA_UPDATE,需要更新的界面监听BAG_DATA_UPDATE消息并刷新界面。

    4.界面打开动画控制。统一使用Animator(Animation),且每个界面最多只有一个Animator(Animation),界面获取焦点时调用Animator的Play方法播放动画。

    5.界面特效控制:游戏会有很多进入界面播放一次的特效,如果你的界面关闭不是使用SetActive处理的(例如设置layer、移到屏幕外等),那么在你的界面再次打开时特效不会再次被播放。

    6.界面内使用的对象怎么获取?(例如要修改界面的某个text)

    每个需要在脚本内加载的对象都挂载一个UIExportItem对象,在界面初始化时统一收集这些对象,并存储在一个map里给界面使用

    7.界面的模糊效果怎么做?可以参考:https://blog.csdn.net/zhaoguanghui2012/article/details/51462284,原理就是使用一个shader均值周围的像素。

    8.弹窗适配,例如实现以下效果:弹窗显示与item的某条边对齐

    如上所示,w为target的包围盒宽度,例如绿色框的长度,h为包围盒的高度,例如绿色框的高度。包围盒大小可以使用unity的RectTransformUtility.CalculateRelativeRectTransformBounds接口获取。

    那么target的坐标怎么结算呢?

    target.x = anchor.x + anchor.w/2 + offect.x + target.w/2

    target.y - target.h/2 - offect.y = anchor.y - anchor.h/2 即 target.y = anchor.y - anchor.h/2 + target.h/2 + offect.y

    当然对齐方式不一样,计算方法也不一样,例如左上、左下等,而且还需要考虑界面布局是否会超框,超框如何处理等。

    具体可以参考:https://blog.csdn.net/SnoopyNa2Co3/article/details/50429604

    UI模块分以下几部分:

    1.界面类:负责界面的逻辑,提供生命周期方法供业务使用,如OnOpen、OnClose等。负责界面的生成、销毁(以及界面ab包的加载、卸载)。子窗口、子item的管理(生成、缓存、销毁)

    2.管理类:提供界面的生命周期管理,如打开、关闭、显示(获得焦点,显示在最上层)、隐藏(失去焦点,可以理解成被其他界面挡住了)一个界面、打开、关闭一堆界面(场景切入、切出时)。缓存界面。维护窗体中间的层级关系。

    3.配置类:负责配置界面的预制体路径、类、界面类型等参数。

    4.功能类:滑动列表、图片镜像、模糊、弹窗适配、通用的标题、通用的tab等。

    5.item类,例如界面的滑动列表的子项。

    导出的层级

     详细代码如下:

    一、ViewDefine:定义类的配置:这边主要是一些界面定义以及界面的配置,通过配置类名可以用反射实例化界面类,通过配置的路径可以加载并实例化预制体。

    using System.Collections.Generic;
    using UnityEngine;
    
    public class ViewDefine 
    {
        public enum ViewType 
        {
            MAIN = 1,      // 主窗口(全屏)
            POPUP = 2,    // 弹窗
            FIXED = 3,    // 固化窗口
            SCENE = 4,    // 场景UI窗口
            GUIDE = 5,    // 引导UI窗口
        }
    
        public enum ViewPopModal
        {
            Blur = 1,                       // 带有模糊效果的模态弹窗
            Lucency_ImPenetrable = 2,       // 无模糊,不可穿透
            Lucency_Penetrate = 3,          // 无模糊, 可穿透
        }
    
        public enum ViewLoadStateDefine
        {
            NONE = 0,
            LOADING = 1,
            LOADED = 2,
        }
    
        public enum ViewOnLoadDefine
        {
            Cache = 1,
            Destroy = 2,
        }
    
        public enum ViewAlignmentType
        {
            UpperLeft = 0,
            UpperCenter = 1,
            UpperRight = 2,
            MiddleLeft = 3,
            MiddleCenter = 4,
            MiddleRight = 5,
            LowerLeft = 6,
            LowerCenter = 7,
            LowerRight = 8,
        }
    
        public enum ViewID
        {
            TOAST,          // 吐司界面
            TOAST_BATTLE,   // 战斗中吐司界面
            NETWAIT,        // 网络等待界面
            NETWORK_TIPS,   // 网络异常提示
        }
    
        private static Dictionary<int, string> _viewConfig = new Dictionary<int, string>
        {
            { (int)ViewID.TOAST,            "ToastView,Prefab/Common/ToastPanel" },
            { (int)ViewID.TOAST_BATTLE,     "ToastBattleView,Prefab/Common/ToastBattlePanel" },
            { (int)ViewID.NETWAIT,          "NetwaitView,Prefab/Common/NetwaitPanel" }
        };
    
        public static string GetViewType(ViewID viewID)
        {
            string config = _viewConfig[(int)viewID];
            if (string.IsNullOrEmpty(config))
            {
                Debug.LogErrorFormat("未配置界面路径 : {0}", viewID);
                return null;
            }
    
            string[] split = config.Split(',');
            return split[0];
        }
    
        public static string GetViewPath(ViewID viewID)
        {
            string config = _viewConfig[(int)viewID];
            if (string.IsNullOrEmpty(config))
            {
                Debug.LogErrorFormat("未配置界面路径 : {0}", viewID);
                return null;
            }
    
            string[] split = config.Split(',');
            return split[1];
        }
    }

    二、UIBase ,ui元素基类,主要提供go的销毁以及导入界面需要引用的对象并保存在_viewObj里,业务可以通过_viewObj["对象名"]访问对象而不用去定义参数。

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// item、view的基类,提供预制体的生成、卸载(ab包的维护),提供子节点的生成
    /// </summary>
    public class UIBase
    {
        protected GameObject gameObject;
        protected Transform transform;
    
        public string Name { get; set; }
    
        protected Dictionary<string, Object> _viewObj = new Dictionary<string, Object>();//导出的界面对象
    
        public virtual void Ctor(GameObject obj, Transform parent)
        {
            if (obj != null)
            {
                gameObject = obj;
                transform = obj.transform;
                ExportHierarchy();
    
                if (parent != null) {
                    transform.SetParent(parent);
                    transform.localPosition = Vector3.zero;
                    transform.localScale = Vector3.zero;
                }
            }
        }
    
        protected virtual void OnLoad() { }
    
        public void SetActive(bool isShow)
        {
            gameObject.SetActive(isShow);
        }
    
        public virtual void Dispose()
        {
            if (gameObject != null) {
                GameObject.Destroy(gameObject);
                transform = null;
                gameObject = null;           
            }
        }
    
        protected void ExportHierarchy()
        {
            if (gameObject)
            {
                UIHierarchy hierarchy = gameObject.GetComponent<UIHierarchy>();
                if (hierarchy)
                {
                    foreach (var item in hierarchy.widgets)
                    {
                        _viewObj.Add(item.name, item.item);
                    }
    
                    foreach (var item in hierarchy.externals)
                    {
                        _viewObj.Add(item.name, item.item);
                    }
                }
            }
        }
    }

    三、UIItemBase ,item类,主要是提供OnItemOpen、OnItemClose方法,方便item在界面打开和关闭时监听(移除)事件

    /// <summary>
    /// 界面item
    /// </summary>
    public class UIItemBase:UIBase
    {
        protected ViewBase parentView;
        protected bool isItemOpen = false;
    
        public virtual void OnItemOpen()
        {
            isItemOpen = true;
        }
    
        public virtual void OnItemClose()
        {
            isItemOpen = false;
        }
    
        public bool IsItemOpen()
        {
            return isItemOpen;
        }
    
        public void SetParentView(ViewBase parent)
        {
            parentView = parent;
        }
    }

    四、PanelBase ,子界面、界面的基类。主要是提供子item的生成、维护。

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// 所有界面的基类,包括子窗口、所有的界面
    /// 这个类主要功能是:提供给子界面生个生成、维护item的接口
    /// </summary>
    public class PanelBase:UIBase
    {
        protected bool isOpen = false;
        protected Dictionary<string, Queue<UIItemBase>> childItemPool = new Dictionary<string, Queue<UIItemBase>>(); //缓存item的池子
        protected List<UIBase> subItems = new List<UIBase>();//维护子对象,包括子窗口、子item
    
        /// <summary>
        /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
        /// </summary>
        /// <param name="args"></param> 界面参数
        public virtual void Open(params object[] args)
        {
            
        }
    
        public virtual void Close()
        {
            
        }
    
        public bool IsOpen()
        {
            return isOpen;
        }
    
        /// <summary>
        /// 生成一个子item
        /// </summary>
        /// <param name="className"></param> 子item的类名
        /// <param name="prefabs"></param> 子item的预制体
        /// <param name="parent"></param> 子item的父节点
        /// <returns></returns>
        protected UIItemBase GenerateItem(string className, GameObject prefabs, Transform parent)
        {
            Queue<UIItemBase> pool = childItemPool[className];
    
            if (pool != null && pool.Count > 0)
            {
                return pool.Dequeue();
            }
    
            UIItemBase item = InstantiateItem(className, prefabs, parent);
            AddSubItem(item);
            return item;
        }
    
        protected void GenerateItemList(string className, GameObject prefabs, Transform parent, int count, ref List<UIItemBase> container)
        {
            while (container.Count < count)
            {
                UIItemBase item = GenerateItem(className, prefabs, transform);
                container.Add(item);
            }
    
            while (container.Count > count)
            {
                UIItemBase item = container[container.Count];
                container.Remove(item);
                RecyleItem(item);
            }
        }
    
        protected UIItemBase InstantiateItem(string className, GameObject prefabs, Transform parent)
        {
            GameObject obj = GameObject.Instantiate(prefabs, parent);
            UIItemBase item = (UIItemBase)UIModule.Instance.CreateUIClass(className);
            item.Ctor(obj, parent);
            item.Name = className;
            return item;
        }
    
        /// <summary>
        /// 回收子item
        /// </summary>
        /// <param name="item"></param>
        protected void RecyleItem(UIItemBase item)
        {
            RemoveSubItem(item);
    
            Queue<UIItemBase> cachePool = childItemPool[item.Name];
            if (cachePool == null)
            {
                cachePool = new Queue<UIItemBase>();
            }
    
            cachePool.Enqueue(item);
            item.SetActive(false);
            if (item.IsItemOpen())
            {
                item.OnItemClose();
            }
        }
    
        protected void RecyleItemList(int count, ref List<UIItemBase> container)
        {
            while (container.Count > count)
            {
                UIItemBase item = container[container.Count];
                RecyleItem(item);
                container.Remove(item);
            }
        }
    
        protected void ClearCache()
        {
            foreach (var pool in childItemPool.Values)
            {
                foreach (var item in pool)
                {
                    item.Dispose();
                }
                pool.Clear();
            }
    
            childItemPool.Clear();
        }
    
        /// <summary>
        /// 添加子对象,包括item、childview
        /// </summary>
        /// <param name="item"></param>
        protected void AddSubItem(UIBase item)
        {
            if (subItems.Contains(item))
            {
                Debug.Log("item已存在");
                return;
            }
    
            subItems.Add(item);
        }
    
        protected void RemoveSubItem(UIBase item)
        {
            if (subItems.Contains(item))
            {
                subItems.Remove(item);
            }
        }
    
        protected void RemoveAllSubItem()
        {
            foreach (var item in subItems)
            {
                item.Dispose();
            }
    
            subItems.Clear();
        }
    
        protected void OnOpenSubItem()
        {
            foreach (var item in subItems)
            {
                if (item is UIItemBase)
                {
                    (item as UIItemBase).OnItemOpen();
                }
            }
        }
    
        protected void OnCloseSubItem()
        {
            foreach (var item in subItems)
            {
                if (item is UIItemBase)
                {
                    (item as UIItemBase).OnItemClose();
                }
            }
        }
    
        public override void Dispose()
        {
            base.Dispose();
    
            RemoveAllSubItem();
            ClearCache();
        }
    }

    五、ChildViewBase :重写了Open、Close方法

    public class ChildViewBase:PanelBase
    {
        /// <summary>
        /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
        /// </summary>
        /// <param name="args"></param> 界面参数
        public override void Open(params object[] args)
        {
            if (!isOpen)
            {
                SetActive(true);
                OnOpenSubItem();
                isOpen = true;
            }
        }
    
        public override void Close()
        {
            if (isOpen)
            {
                OnCloseSubItem();
                SetActive(false);
                isOpen = false;
            }
        }
    }

    六、ViewBase :所有界面基类,主要是提供了界面的加载、卸载(注意ab的加、卸载),子界面的添加、维护。

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// 界面基类,可以生成ChildView
    /// </summary>
    public class ViewBase: PanelBase
    {
        protected object[] openParam;
        protected ViewDefine.ViewType viewType;
        protected ViewDefine.ViewLoadStateDefine loadState = ViewDefine.ViewLoadStateDefine.NONE;
        protected bool isHide = false;
        protected float closeTime = 0;//用于回收计时
    
        public ViewDefine.ViewID ViewID { get; set; }
    
        /// <summary>
        /// 打开界面,一次OnOpen对应一次OnClose,子类实现
        /// </summary>
        /// <param name="args"></param>
        protected virtual void OnOpen(params object[] args){ }
    
        /// <summary>
        /// 用于刷新界面,每次调用OpenView都会调用,避免业务重复打开界面
        /// </summary>
        /// <param name="args"></param>
        protected virtual void OnRefreshView(params object[] args) { }
    
        /// <summary>
        /// 获得焦点。1.打开界面。2.上一层界面被关闭,重新获得焦点
        /// </summary>
        protected virtual void OnEnabled() { }
    
        public virtual void Update(float dt) { }
    
        /// <summary>
        /// 失去焦点。1.关闭界面。2.有新的界面打开。
        /// </summary>
        protected virtual void OnDisable() { }
    
        protected virtual void OnClose() { }
    
    
        /// <summary>
        /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
        /// </summary>
        /// <param name="args"></param> 界面参数
        public override void Open(params object[] args)
        {
            openParam = args;
            if (loadState == ViewDefine.ViewLoadStateDefine.LOADED)
            {
                DoRealOpen();
            }
            else
            {
                Load();
            }
        }
    
        /// <summary>
        /// 加载界面,包括界面的ab包,ab的依赖包,最终返回一个Prefab用于实例化界面
        /// </summary>
        private void Load()
        {
            if (loadState != ViewDefine.ViewLoadStateDefine.NONE) return;
    
            loadState = ViewDefine.ViewLoadStateDefine.LOADING;
            ResManager.Instance.LoadPrefab(ViewDefine.GetViewPath(ViewID), "", LoadFinish);      
        }
    
        private void LoadFinish(object prefab)
        {
            loadState = ViewDefine.ViewLoadStateDefine.LOADED;
            gameObject = GameObject.Instantiate((GameObject)prefab, UIModule.Instance.GetViewRoot(viewType));
            transform = gameObject.transform;
            ExportHierarchy();
    
            OnLoad();
            DoRealOpen();
        }
    
        private void DoRealOpen()
        {
            if (!isOpen)
            {
                SetActiveEx(true);
                OnOpen(openParam);
                OnOpenSubItem();
            }
    
            OnRefreshView(openParam);
            ShowView();
        }
    
        /// <summary>
        /// 显示、隐藏界面,这边使用的方式是将界面移到屏幕外。
        /// 另外几种做法是:1.SetActive直接隐藏go。2.设置Scale为0。3.设置layer out
        /// </summary>
        /// <param name="isActive"></param> 是否显示
        public void SetActiveEx(bool isActive)
        {
            if (transform) {
                if (isActive)
                {
                    transform.localPosition = Vector3.zero;
                }
                else
                {
                    transform.localPosition = new Vector3(10000, 10000, 0);
                }
            }
        }
    
        public virtual void ShowView()
        {
            SetActiveEx(true);
            transform.SetAsFirstSibling();
    
            if (!isHide)
            {
                isHide = true;
                OnEnabled();
            }       
        }
    
        /// <summary>
        /// 界面失去焦点,如果是打开弹窗,不隐藏该界面。
        /// </summary>
        /// <param name="keepShow"></param>
        public virtual void HideView(bool keepShow = false)
        {
            SetActiveEx(keepShow);
    
            if (isHide)
            {
                isHide = false;
                OnDisable();
            }
        }
    
        public override void Close()
        {
            if (isOpen)
            {
                UIModule.Instance.CloseView(ViewID);
            }
        }
    
        public virtual void CloseView()
        {
            HideView();
            if (isOpen)
            {
                OnClose();
                OnCloseSubItem();
                CloseSubPanel();
                closeTime = Time.realtimeSinceStartup;
            }
        }
    
        public float GetCloseTime()
        {
            return closeTime;
        }
    
        public bool IsPopView()
        {
            return viewType == ViewDefine.ViewType.POPUP;
        }
    
        public bool IsMainView()
        {
            return viewType == ViewDefine.ViewType.MAIN;
        }
    
        public bool IsFixedView()
        {
            return viewType == ViewDefine.ViewType.FIXED;
        }
    
        public bool IsShow()
        {
            return !isHide;
        }
    
        public bool IsOnLoadDestroy()
        {
            return loadState == ViewDefine.ViewLoadStateDefine.LOADING;
        }
    
        protected List<ChildViewBase> childView = new List<ChildViewBase>();
        protected ChildViewBase AddChildPanel(string className, GameObject obj, Transform parent)
        {
            ChildViewBase view = (ChildViewBase)UIModule.Instance.CreateUIClass(className);
            view.Ctor(obj, parent);
            childView.Add(view);
            AddSubItem(view);
            return view;
        }
    
        protected void CloseSubPanel()
        {
            foreach (var item in childView)
            {
                if (item.IsOpen())
                {
                    item.Close();
                }
            }
        }
    }

    七、UIHierarchy:保存了业务导出的引用对象。ExportPanelHierarchy寻找UIExportItem 元素并保存到UIHierarchy

    public class UIHierarchy : MonoBehaviour
    {
        [System.Serializable]
        public class ItemInfo
        {
            public string name;
            public Object item;
    
            public ItemInfo() { }
            public ItemInfo(string _name, Object _item) { name = _name; item = _item; }
        }
    
        // 控件
        public List<ItemInfo> widgets;
        public void SetWidgets(List<ItemInfo> data)
        {
            if (data.Count == 0) return;
            if (widgets == null)
            {
                widgets = new List<ItemInfo>();
            }
    
            widgets.Clear();
            widgets.AddRange(data);
        }
    
        // 外部引用
        public List<ItemInfo> externals;
    }
    
    using UnityEngine;
    
    [DisallowMultipleComponent]
    public class UIExportItem : MonoBehaviour
    {
        public string FieldName;
    }
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEditor;
    using System.Collections.Generic;
    
    public class ExportPanelHierarchy
    {
        /// <summary>
        /// 导出组件优先级
        /// </summary>
        private static System.Type[] ms_componentTypes = {
            typeof(Button),
            typeof(InputField),
            typeof(ScrollRect),
            typeof(Dropdown),
            typeof(Image),
            typeof(RawImage),
            typeof(Scrollbar),
            typeof(Slider),
            typeof(Text),
            typeof(Toggle),
            typeof(GridLayoutGroup),
            typeof(HorizontalOrVerticalLayoutGroup),
            typeof(LayoutElement),        
            typeof(CanvasGroup),
            typeof(ToggleGroup),
            typeof(TextMesh),
            typeof(Animation),
            typeof(Camera),
            typeof(SpriteRenderer),
        };
    
        static Object FindComponent(GameObject go)
        {
            Object component = null;
            for (int i = 0; i < ms_componentTypes.Length; ++i)
            {
                component = go.GetComponent(ms_componentTypes[i]);
                if (component != null)
                {
                    break;
                }
            }
            return component;
        }
    
        //生成嵌套UI层级
        public static void ExportNested(Object obj)
        {
            GameObject root = obj as GameObject;
            if (root == null) return;
    
            UIHierarchy hierarchy = root.GetComponent<UIHierarchy> ();
            if(hierarchy==null)
            {
                hierarchy = root.AddComponent<UIHierarchy>();
            }
    
            //生成根节点层级
            List<UIHierarchy.ItemInfo> fields = new List<UIHierarchy.ItemInfo>();
            GetChildComponentUtilHierarchy (root.transform, fields);
            hierarchy.SetWidgets(fields);
    
            //生成子panel层级
            UIHierarchy[] childHierarchys = root.GetComponentsInChildren<UIHierarchy>(true);
            for(int i=1; i<childHierarchys.Length; i++)
            {
                UIHierarchy childHrcy = childHierarchys [i];
    
                List<UIHierarchy.ItemInfo> childUIItem = new List<UIHierarchy.ItemInfo>();
                GetChildComponentUtilHierarchy (childHrcy.transform, childUIItem);
                childHrcy.SetWidgets(childUIItem);
            }
    
            EditorUtility.SetDirty(root);
            AssetDatabase.SaveAssets();
        }
        
        //导出传入节点的层级,直到某个子节点挂有UIHierarchy组件
        private static void GetChildComponentUtilHierarchy(Transform transRoot, List<UIHierarchy.ItemInfo> fields)
        {
            for(int i=0; i<transRoot.childCount; i++)
            {
                Transform trans = transRoot.GetChild (i);
    
                UIHierarchy hrchy = trans.GetComponent<UIHierarchy> ();
                if(hrchy!=null)
                {
                    fields.Add (new UIHierarchy.ItemInfo(hrchy.name, hrchy));
                    continue;
                }
    
                UIExportItem uiItem = trans.GetComponent<UIExportItem>();
                if (uiItem != null)
                {
                    Object fieldItem = FindComponent(uiItem.gameObject);
                    if (fieldItem == null)
                    {
                        fieldItem = uiItem.transform;
                    }
                    fields.Add(new UIHierarchy.ItemInfo(uiItem.name, fieldItem));
                }
    
                GetChildComponentUtilHierarchy (trans, fields);
            }
        }
    }

    八、UIModule:核心管理类,提供给全局唯一的打开界面方法,维护层级栈。维护界面缓存。

    using System.Collections.Generic;
    using System.Reflection;
    using UnityEngine;
    
    public class UIModule
    {
        #region Instance
        private static UIModule m_Instance;
        public static UIModule Instance
        {
            get
            {
                return m_Instance ?? (m_Instance = new UIModule());
            }
        }
        #endregion
    
        private const float CACHE_TIME = 30;//界面缓存时间
        private int curSceneID = 0;//当前打开的场景id,每个场景都有自己的层级栈
        private int curViewID = 0;//当前打开的界面id
        private int lastViewID = 0;//上一个界面id
    
        private List<int> openViewList;//当前打开的界面列表,按顺序
        private Dictionary<int, ViewBase> cacheView;//缓存区,等待销毁,从缓存区取要移除
        private Dictionary<int, List<int>> naviStack;//场景的层级栈,key为场景id,用于维护场景
        private Dictionary<int, ViewBase> viewPool;//保存所有ViewBase的引用,包括缓存区的,界面销毁时需要移除。
        private Dictionary<ViewDefine.ViewType, Transform> viewRoot;//界面实例化出来的父节点,不同界面的父节点不一样。
        private Transform uiRoot;//ui根节点
        private float lastCheckCacheTime;//上次检查缓存时间,一秒检查一次
    
        public void Init()
        {
            openViewList = new List<int>();
            cacheView = new Dictionary<int, ViewBase>();
            naviStack = new Dictionary<int, List<int>>();
            viewPool = new Dictionary<int, ViewBase>();
    
            uiRoot = GameObject.Find("UIRoot").transform;
            viewRoot = new Dictionary<ViewDefine.ViewType, Transform>
            {
                { ViewDefine.ViewType.MAIN, uiRoot.Find("main") },
                { ViewDefine.ViewType.POPUP, uiRoot.Find("main") },
                { ViewDefine.ViewType.FIXED, uiRoot.Find("fixed") },
                { ViewDefine.ViewType.GUIDE, uiRoot.Find("guide") }
            };
    
            lastCheckCacheTime = Time.realtimeSinceStartup;
        }
    
        public Transform GetViewRoot(ViewDefine.ViewType viewType)
        {
            if (viewType == ViewDefine.ViewType.SCENE)
            {
                return null;
            }
            else
            {
                return viewRoot[viewType];
            }
        }
    
        public void Update(float dt)
        {
            int openCount = openViewList.Count;
            ViewBase view;
            for (int i = 0; i < openCount; i++)
            {
                int viewId = openViewList[i];
                if (viewPool.TryGetValue(viewId, out view))
                {
                    view.Update(dt);
                }
            }
    
            //清楚缓存时间到了的界面
            float curTime = Time.realtimeSinceStartup;
            if (curTime - lastCheckCacheTime > 1)
            {
                foreach (var item in cacheView)
                {
                    if (curTime - item.Value.GetCloseTime() > CACHE_TIME)
                    {
                        item.Value.Dispose();
                        cacheView.Remove(item.Key);
                        viewPool.Remove(item.Key);
                    }
                }
            }
    
            lastCheckCacheTime = curTime;
        }
    
        /// <summary>
        /// 外部调用 打开一个窗口的唯一方式
        /// 如果上一个界面lastView存在,需要把lastView入栈
        /// </summary>
        /// <param name="viewID"></param>
        /// <param name="param"></param> 界面打开参数,给业务使用的
        /// <returns></returns>
        public ViewBase OpenView(ViewDefine.ViewID viewID, params object[] param)
        {
            int viewKey = (int)viewID;
            ViewBase view = FindOpenView(viewKey);
    
            if (view == null)
            {
                view = CreateView(viewID);
    
                if (view == null)
                {
                    Debug.LogErrorFormat("OpenView CreateView Fail..viewID:", viewID);
                    return null;
                }
                view.ViewID = viewID;
                AddOpenView(viewKey);
            }
    
            if (curViewID == viewKey)
            {
                view.Open(param);
                return view;
            }
    
            if (view.IsPopView() || view.IsMainView())
            {
                lastViewID = curViewID;
                curViewID = viewKey;
    
                if (lastViewID > 0)
                {
                    OnBackstage(lastViewID);
                }
            }
    
            view.Open(param);
            return view;
        }
    
        private ViewBase FindOpenView(int viewKey)
        {
            ViewBase view = null;
            if (openViewList.Contains(viewKey))
            {            
                viewPool.TryGetValue(viewKey, out view);
            }
    
            return view;
        }
    
        private void AddOpenView(int viewKey)
        {
            if (openViewList.Contains(viewKey))
            {
                Debug.LogFormat("界面已打开:{0}", viewKey);
                return;
            }
            openViewList.Add(viewKey);
        }
    
        private void RemoveOpenView(int viewKey)
        {
            if (openViewList.Contains(viewKey))
            {
                openViewList.Remove(viewKey);
            }
        }
    
        /// <summary>
        /// 创建新的view,先从缓存里面找
        /// </summary>
        /// <param name="viewID"></param>
        /// <returns></returns>
        private ViewBase CreateView(ViewDefine.ViewID viewID)
        {
            int viewKey = (int)viewID;
            ViewBase view = GetViewFromCache(viewKey);
    
            if (view == null)
            {
                string viewName = ViewDefine.GetViewType(viewID);
                //加载程序集,创建程序集里面的 命名空间.类型名 实例
                object ect = CreateUIClass(viewName);
    
                view = (ViewBase)ect;//类型转换并返回
                viewPool.Add((int)viewID, view);
            }
            
            return view;
        }
    
        public object CreateUIClass(string calssName)
        {
            return Assembly.GetExecutingAssembly().CreateInstance(calssName);
        }
    
        private ViewBase GetViewFromCache(int viewKey)
        {
            ViewBase view = null;
            if (cacheView.TryGetValue(viewKey, out view)) {
                cacheView.Remove(viewKey);
            }
            
            return view;
        }
    
        private void AddViewToCache(int viewKey, ViewBase view)
        {
            cacheView[viewKey] = view;
        }
    
        private int GetTopViewOfStack()
        {
            List<int> stack = naviStack[curSceneID];
            if (stack == null || stack.Count == 0)
            {
                return 0;
            }
    
            return stack[stack.Count];
        }
    
        private ViewBase PopViewFormStack()
        {
            List<int> stack = naviStack[curSceneID];
            if (stack == null || stack.Count == 0)
            {
                return null;
            }
    
            int index = stack.Count;
            int viewId = stack[index];
            ViewBase view = GetViewByKey(viewId);
            stack.RemoveAt(index);
    
            if (view.IsPopView())
            {
                for (int i = index-1; i > 0; i--)
                {
                    ViewBase temp = GetViewByKey(stack[i]);
                    temp.SetActiveEx(true);
    
                    if (temp.IsMainView()) break;
                }
            }
    
            curViewID = viewId;
            view.ShowView();
            AddOpenView(viewId);
            return view;
        }
    
        /// <summary>
        /// 向栈里添加元素
        /// </summary>
        /// <param name="viewKey"></param>
        /// <param name="isForce"></param>true时,栈里存在会先移除在加入,否则栈里存在就不处理了
        private void AddViewToStack(int viewKey, bool isForce = true)
        {
            List<int> stack = naviStack[curSceneID];
            if (stack == null)
            {
                stack = new List<int>();
            }
    
            if (stack.Contains(viewKey))
            {
                if (isForce)
                {
                    stack.Remove(viewKey);
                }
                else
                {
                    return;
                }            
            }
    
            stack.Add(viewKey);
        }
    
        private void RemoveFromStack(int viewKey)
        {
            List<int> stack = naviStack[curSceneID];
            if (stack == null || stack.Count == 0)
            {
                return;
            }
    
            if (stack.Contains(viewKey))
            {
                stack.Remove(viewKey);
            }
        }
    
        /// <summary>
        /// 进入后台
        /// </summary>
        /// <param name="viewKey"></param>
        private void OnBackstage(int viewKey)
        {
            AddViewToStack(viewKey);
            ViewBase lastView = GetViewByKey(lastViewID);
    
            if (lastView.IsPopView())
            {
                lastView.HideView(true);
            }
            else
            {
                lastView.HideView(false);
            }        
        }
    
        /// <summary>
        /// 关闭界面,如果是当前打开界面,需要从栈顶弹出新的界面
        /// </summary>
        /// <param name="viewKey"></param>
        private void InsertClose(int viewKey)
        {
            RemoveView(viewKey);
    
            if (viewKey == curViewID)
            {
                curViewID = 0;
                PopViewFormStack();
            }
            else
            {
                RemoveFromStack(viewKey);
            }
        }
    
        /// <summary>
        /// 移除界面,从打开列表移除,添加到缓存
        /// </summary>
        /// <param name="viewKey"></param>
        private void RemoveView(int viewKey)
        {
            RemoveOpenView(viewKey);
            RemoveFromStack(viewKey);
            ViewBase view = GetViewByKey(viewKey);
            view.CloseView();
            AddViewToCache(viewKey, view);
        }
    
        private ViewBase GetViewByKey(int viewKey)
        {
            return viewPool[viewKey];
        }
    
        public void CloseView(ViewDefine.ViewID viewID)
        {
            int viewKey = (int)viewID;
            if (openViewList.Contains(viewKey))
            {
                InsertClose(viewKey);
            }
        }
    
        public void CloseCurView()
        {
            if (curViewID > 0)
            {
                CloseView((ViewDefine.ViewID)curViewID);
            }
        }
    
        /// <summary>
        /// 进入新的场景
        /// </summary>
        /// <param name="sceneID"></param> 场景id
        /// <param name="isNative"></param> 是否需要打开ui栈,isBack=true时,从当前场景的栈顶弹出界面
        public void EnterScene(int sceneID, bool isBack)
        {
            curSceneID = sceneID;
            if (naviStack[sceneID] == null)
            {
                naviStack[sceneID] = new List<int>();
            }
    
            if (isBack)
            {
                PopViewFormStack();
            }
        }
    
        /// <summary>
        /// 退出当前场景
        /// </summary>
        /// <param name="pushToStack"></param> 是否压栈,用于场景返回时恢复ui层级
        public void ExitScene(bool pushToStack)
        {
            int count = openViewList.Count;
            ViewBase temp = null;
            int viewKey = 0;
    
            for (int i = count; i > 0; i--)
            {
                viewKey = openViewList[i];
                temp = GetViewByKey(viewKey);
    
                if (temp != null && (temp.IsMainView() || temp.IsPopView()))
                {
                    if (pushToStack)
                    {
                        RemoveOpenView(viewKey);
                        AddViewToStack(viewKey, false);
                    }
                    else
                    {
                        RemoveView(viewKey);
                    }
                }
            }
    
            List<int> stack = naviStack[curSceneID];
            if (stack != null)
            {
                for (int i = 0; i < stack.Count; i++)
                {
                    ViewBase view = GetViewByKey(stack[i]);
                    view.HideView();
                }
            }
    
            curViewID = 0;
            lastViewID = 0;
        }
    }
  • 相关阅读:
    ffmpeg中的数据结构(AVFormatContext)
    string转wstring
    Bento4
    根据代码结构和注释生成接口文档
    IOS摄像头采和显示
    git主分支覆盖子分支
    Nginx 面试 40 连问,快顶不住了
    数据库日期类型字段设计,应该如何选择
    java各种输入输出流
    android 之 linkToDeath和unlinkToDeath。(死亡代理)
  • 原文地址:https://www.cnblogs.com/wang-jin-fu/p/11252256.html
Copyright © 2020-2023  润新知