用栈来管理UI界面:ViewManager
上一个项目里我的工作主要在UI上,于是就写了这个ViewManager,以栈的形式来管理各个UI界面。
主要分为两个部分:一个界面管理器ViewManager和一个界面的的基类IView。考虑到切场景的情况,所以还会有一个切场景的管理器SceneManager。
栈元素结构
----------------------------------------------------------------------------------
一般的View可能会有 弹窗类型,顶掉上一个界面的类型以及是否能被返回这么几种类型。所以View应该要有一个ViewType来标记界面类型。
因为先前入栈的界面,它们的View实例在游戏中有可能会被销毁掉,比如因为跳转场景就销毁了当前场景下的所有View实例,所以栈中存放的应该是一个能够重新拿到View实例的关键字,而不是View实例本身。这里以一个name来作这个关键字比较合适。
那么原本存在view实例里的viewType这时候也应该存个备份到栈里了,这样view实例被销毁后ViewManager还能知道这个view对应的viewtype.
考虑到跳转场景的因素,所以栈里还要再存一个场景标记,就用场景名好了
那么就建了一个栈元素的结构体。
public struct ViewInfo
{
public ViewInfo(string scene,string name,ViewType viewtype)
{
this.scene = scene;
this.name = name;
this.viewType = viewtype;
}
public string scene;
public string name;
public ViewType viewType;
}
跳转界面JumpTo
--------------------------------------------------------------------------------------
本来跳界面跳转的逻辑是很简单的,但是一考虑到跳转场景就变得比较复杂了,而且也容易出问题,所以应该尽量以单场景的方式开发。结构上比较简单。不过对于跳转场景的情况还是应该考虑进来的。
记一个curScene表示当前场景,若跳转界面的时候发现跟当前场景不一致则先跳转场景。
public static void JumpTo(string scene,string viewName,ViewType? type=null )
{
if (!string.IsNullOrEmpty(curScene) && scene.Equals(curScene))
{
EnterView(viewName, type);
} else
{
EnterScene(scene, delegate ()
{
EnterView(viewName, type);
});
}
}
static void EnterView(string viewName,ViewType? type)
{
//若将进入的界面存在且不是悬浮界面 则先退出当前界面即栈顶界面
var curView = GetTopView();
if (!string.IsNullOrEmpty(curView.name)&&type !=null&&(type & ViewType.levitate) <= 0)
{
if (viewPool[curView.name] != null)
viewPool[curView.name].Exit();
}
if (type == null)//默认界面类型 不过可能会在运行时被修改
{
IView view = GetView(viewName);
type = view == null ? default(ViewType) : view.viewType;
}
ViewInfo kv = new ViewInfo(curScene, viewName, type.Value);
PushView(kv);
OpenView(kv);
}
static void OpenView(ViewInfo kv)
{
if (kv.scene.Equals(curScene))
{
IView view = GetView(kv.name);
if (view != null)
{
view.SetViewType(kv.viewType);
view.Enter();
}
}
else
{
EnterScene(kv.scene, delegate ()
{
OpenView(kv);
});
}
}
static void EnterScene(string scene,System.Action callBack)
{
SceneLoadManager.Instance.LoadSceneAsync(scene, delegate ()
{
curScene = scene;
if (callBack != null)
callBack();
});
}
回退BackView
---------------------------------------------------------------------------------
栈深度大于1时才能回退
public static void BackView()
{
if(ViewStack.Count>1)
{
ViewInfo popView=PopView();
ViewInfo peekView = GetTopView();
//若当前界面为悬浮界面,则只退出当前界面,不用重进栈顶 因为悬浮界面不会顶掉原界面
if(!string.IsNullOrEmpty(popView.name)&&(popView.viewType&ViewType.levitate)>0)
{
if (viewPool[popView.name] != null)
{
viewPool[popView.name].Exit();
}
//若发生过场景切换或界面已被销毁 则重新打开一下
if (!peekView.scene.Equals(curScene)||viewPool[peekView.name]==null)
{
OpenView(peekView);
}
}
else
{
//寻找一个可以返回的界面
while(!string.IsNullOrEmpty(peekView.name)&&(peekView.viewType&ViewType.cantBack)>0)
{
PopView();
peekView = GetTopView();
}
//若不需要跳转场景并且当前界面存在 则退出当前界面
if(viewPool[popView.name]!=null&&popView.scene.Equals(curScene))
viewPool[popView.name].Exit();
OpenView(peekView);
}
#if UNITY_EDITOR
LogStackInfo();
#endif
}
#if UNITY_EDITOR
else
{
Debug.LogError("Can't BackView:stack depth is 1");
}
#endif
}
只跳一个场景的情况
----------------------------------------------------------------------------
考虑单纯地跳一个场景的情况。同样也会入栈,但是name字段为null。BackView方法也同样生效,viewType也能生效,可以用来区分当前场景能否被返回
public static void JumpToScene(string scene,ViewType type=default(ViewType))
{
JumpTo(scene, null, type);
}
完整代码
------------------------------------------------------------------------
public class ViewManager : MonoBehaviour {
private static Stack ViewStack;
private static Dictionary viewPool;
private static string curScene;//当前场景名
private static GameObject _viewRoot;
private static GameObject viewRoot
{
get
{
if (_viewRoot == null)
_viewRoot = GameObject.FindWithTag("ViewRoot");
return _viewRoot;
}
}
void Awake()
{
ViewStack = new Stack();
viewPool = new Dictionary();
}
void Update()
{
//返回键
if (Input.GetKeyDown(KeyCode.Escape))
{
var curView = GetTopView();
if(!string.IsNullOrEmpty(curView.name))
{
GetView(curView.name).BackView();
}
}
}
#region EnterView
public static void JumpToScene(string scene,ViewType type=default(ViewType))
{
JumpTo(scene, null, type);
}
public static void JumpTo(string viewName, ViewType? type=null)
{
JumpTo(curScene, viewName, type);
}
public static void JumpTo(string scene,string viewName,ViewType? type=null )
{
if (!string.IsNullOrEmpty(curScene) && scene.Equals(curScene))
{
EnterView(viewName, type);
} else
{
EnterScene(scene, delegate ()
{
EnterView(viewName, type);
});
}
}
static void EnterView(string viewName,ViewType? type)
{
//若将进入的界面存在且不是悬浮界面 则先退出当前界面即栈顶界面
var curView = GetTopView();
if (!string.IsNullOrEmpty(curView.name)&&type !=null&&(type & ViewType.levitate) <= 0 )
{
if (viewPool[curView.name] != null)
viewPool[curView.name].Exit();
}
if (type == null)//设置默认界面类型 不过可能会在运行时被修改
{
IView view = GetView(viewName);
type = view == null ? default(ViewType) : view.viewType;
}
ViewInfo kv = new ViewInfo(curScene, viewName, type.Value);
PushView(kv);
OpenView(kv);
}
static void OpenView(ViewInfo kv)
{
if (kv.scene.Equals(curScene))
{
IView view = GetView(kv.name);
if (view != null)
{
view.SetViewType(kv.viewType);
view.Enter();
}
}
else
{
EnterScene(kv.scene, delegate ()
{
OpenView(kv);
});
}
}
static void EnterScene(string scene,System.Action callBack)
{
SceneLoadManager.Instance.LoadSceneAsync(scene, delegate ()
{
curScene = scene;
if (callBack != null)
callBack();
});
}
#endregion
public static void BackView()
{
if(ViewStack.Count>1)
{
ViewInfo popView=PopView();
ViewInfo peekView = GetTopView();
//若当前界面为悬浮界面,则只退出当前界面,不用重进栈顶
if(!string.IsNullOrEmpty(popView.name)&&(popView.viewType&ViewType.levitate)>0)
{
if (viewPool[popView.name] != null)
{
viewPool[popView.name].Exit();
}
//若发生过场景切换或界面已被销毁 则重新打开一下
if (!peekView.scene.Equals(curScene)||viewPool[peekView.name]==null)
{
OpenView(peekView);
}
}
else
{
//寻找一个可以返回的界面
while(!string.IsNullOrEmpty(peekView.name)&&(peekView.viewType&ViewType.cantBack)>0)
{
PopView();
peekView = GetTopView();
}
//若不需要跳转场景并且当前界面存在 则退出当前界面
if(viewPool[popView.name]!=null&&popView.scene.Equals(curScene))
viewPool[popView.name].Exit();
OpenView(peekView);
}
#if UNITY_EDITOR
LogStackInfo();
#endif
}
#if UNITY_EDITOR
else
{
Debug.LogError("Can't BackView:stack depth is 1");
}
#endif
}
static ViewInfo PopView()
{
return ViewStack.Pop();
}
static void PushView(ViewInfo kv)
{
ViewStack.Push(kv);
#if UNITY_EDITOR
LogStackInfo();
#endif
}
public static IView GetView(string name)
{
if (string.IsNullOrEmpty(name)) return null;
if (viewPool.ContainsKey(name) && viewPool[name] != null)
{
return viewPool[name];
}
GameObject obj = null;//MemoryPool.GetObjInstanceForNGUI(name, viewRoot);
IView view = null;
if (obj != null)
{
view = obj.GetComponent();
}
if (!viewPool.ContainsKey(name))
viewPool.Add(name, view);
else
viewPool[name] = view;
return view;
}
public static int GetViewDepth()
{
return ViewStack.Count;
}
public static ViewInfo GetTopView()
{
return ViewStack.Peek();
}
public static void ClearViewStack()
{
ViewStack.Clear();
}
static void LogStackInfo()
{
#if UNITY_EDITOR
Debug.Log("----------------------------------------------------------------------------------------------------");
Debug.Log("StackCount:" + ViewStack.Count);
var stack = ViewStack.ToArray();
for (int i = 0, Imax = stack.Length; i < Imax; i++)
{
Debug.Log(stack[i].ToString());
}
Debug.Log("----------------------------------------------------------------------------------------------------");
#endif
}
public struct ViewInfo
{
public ViewInfo(string scene,string name,ViewType viewtype)
{
this.scene = scene;
this.name = name;
this.viewType = viewtype;
}
public string scene;
public string name;
public ViewType viewType;
public override string ToString()
{
string str="Scene:【" + scene + "】 Name:【" + (name == null ? "null" : name) + "】 viewType:";
string usingViewType=string.Empty;
foreach(ViewType type in System.Enum.GetValues((typeof(ViewType))))
{
if((viewType&type)>=type)
{
usingViewType += type.ToString()+"|";
}
}
str += "【" + usingViewType + "】";
return str;
}
}
}
- 上一篇: 一个编码问题造成的打不开文件夹
- 下一篇: 用栈来管理UI界面:IView