框架之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里给界面使用

UI模块分以下几部分:

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

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

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

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

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

详细代码如下:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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[“对象名”]访问对象而不用去定义参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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在界面打开和关闭时监听(移除)事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <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的生成、维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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的加、卸载),子界面的添加、维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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:核心管理类,提供给全局唯一的打开界面方法,维护层级栈。维护界面缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
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;
}
}