写在前面的话
我会先把源码直接给出来。博主自己也不是什么大牛,因此写的也不是什么很厉害的框架,只是一个很小很小的UI框架,给初学者一点入门的思路。
不少人在刚开始接触框架的时候都会有一种感受:这NM什么DDX啊?!彻底懵逼了有没有?!我还没开始写代码呢!怎么就已经有这么多代码要看啊?!还有大部分新人都会吐槽的事情:为何要这么写啊?这玩意随便写个脚本挂上去不是分分钟实现功能吗?你这绕来绕去搞毛?这个主程是否是NT?!emmmmmmmmmmm…若是你知道主程怎么想的,你不就是主程了?
因此,初次接触框架的话,能够抱怨,可是请尽快冷静下来,好好看代码。起码要以最快的速度会用这个框架,在慢慢去分析为何框架要这么写。其实看完后你就会发现,主程写的框架实现一个功能确实很麻烦,可是你用起来的时候却很是方便,由于不少东西主程早早的就给你封装在框架里了,你只须要干一些不动脑子的体力活就实现了需求,爽就一个字!这就是为何要使用框架,为何主程要弯弯绕绕的实现功能。
json
好,若是你作好了思想准备,那么就先下载源码,读起来吧!这真的是一个很简单的UI框架,内容很少。读起来应该很容易。
UIFrame
提取码:gt4a
canvas
那接下来咱们就开始吧!数组
初次接触框架的读者可能会彻底不知道从哪里下手阅读,那么博主就带着你们一块儿看看,这个框架主要是用来解决什么问题的。缓存
脚本文件夹结构
这个框架有五个基本文件夹,分别是:Managers,Models,UIBases,UIInterface,Utility。
怎么样?都是你们耳熟能详的名字吧?那么咱们应该先从哪里开始阅读呢?天然就是Utility文件夹了。
网络
Utility文件夹下的脚本
打开这个文件夹,有以下脚本:
下面我将依次讲解这几个脚本各自负责作些什么。
首先,咱们先看Singlton:
框架
using System; namespace UIFrame { public class Singleton<T> where T : class { private static T instance; public static T Instance { get { if (instance == null) { instance = (T)Activator.CreateInstance(typeof(T), true); } return instance; } } } }
很简单,只有20行代码。这个脚本只作了一件事,就是实现一个单例。特殊之处在于,这是一个单例的带有泛型的基类,使用反射的方式搞定的单例,那么他的做用呢?也很简单,只要是你想要把一个脚本作成单例模式,那么只须要继承这个脚本,再私有化无参构造便可。文字可能解释的不是很清楚,咱们结合代码来看,接下来咱们来看看AssetsManager:dom
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class AssetsManager : Singleton<AssetsManager> { private AssetsManager() { assetsChace = new Dictionary<string, object>(); } /// <summary> /// 资源缓存池 /// </summary> private Dictionary<string, object> assetsChace; /// <summary> /// 获取资源方法 /// </summary> /// <param name="assetPath">路径</param> /// <returns>获取到的对象</returns> public object GetAsset(string assetPath) { object asset = null; if (!assetsChace.TryGetValue(assetPath,out asset)) { asset = Resources.Load(assetPath); assetsChace.Add(assetPath, asset); } return asset; } } }
AssetsManager脚本是我用来作资源管理的脚本,那么可能会在不少其余地方要用到,那每次要用到我都去实例化就太麻烦了,并且也不利于资源管理,因此我把他作成单例。怎么作?就利用上面的Singleton脚原本实现,咱们让AssetsManager继承Singleton,再私有化构造函数,就天然而然的完成了咱们的需求,之后再有其它脚本要实现单例,也这样如法炮制便可。这里就已经体现了一些些框架的威力。也能够说是设计原则的好处。不展开了,回到正题。那么这个脚本管理的是什么资源呢?其实,什么资源均可以,但咱们主要是针对UI资源进行管理,首先咱们写了一个缓存池,用来缓存已经取过的资源用以二次利用。而后就是一个获取资源的方法了,很简单,就是传过来一个资源的所在路径,经过路径将资源拿出来放进缓存后返回该资源,本身看看应该就能看懂。
好,那这个脚本就很少说了,咱们看下一个,JsonPanelManager:
ide
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class JsonPanelManager : Singleton<JsonPanelManager> { private JsonPanelManager() { jsonPanelDataDic = new Dictionary<int, Dictionary<string, string>>(); jsonLocalizationDic = new Dictionary<int, Dictionary<string, string[]>>(); widgetDataDic = new Dictionary<int, Dictionary<string, string>>(); ParseJsonPanel(); //ParseJsonLocalization(); ParseJsonWidget(); } /// <summary> /// json转换的对象 /// </summary> private JsonPanelModel jsonPanelData; /// <summary> /// json转换后的字典存储 /// </summary> private Dictionary<int, Dictionary<string,string>> jsonPanelDataDic; /// <summary> /// json转化后的本地化文件对象 /// </summary> private JsonLocalizationModel jsonLocalizationData; /// <summary> /// json转换后的本地化文件字典 /// </summary> private Dictionary<int, Dictionary<string, string[]>> jsonLocalizationDic; //Widget解析后的数据 private JsonWidgetModel widgetData; //Widget解析后的数据【字典版】 private Dictionary<int, Dictionary<string, string>> widgetDataDic; /// <summary> /// json解析 /// </summary> private void ParseJsonPanel() { //获取json文本对象 TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonPanelsPath) as TextAsset; //解析json jsonPanelData = JsonUtility.FromJson<JsonPanelModel>(assetText.text); for (int i = 0; i < jsonPanelData.AllData.Length; i++) { //新定义一个字典 Dictionary<string, string> crtPanelData = new Dictionary<string, string>(); //将场景ID和字典存入 jsonPanelDataDic.Add(i, crtPanelData); //将场景的全部资源路径保存 for (int j = 0; j < jsonPanelData.AllData[i].Data.Length; j++) { crtPanelData.Add(jsonPanelData.AllData[i].Data[j].PanelName, jsonPanelData.AllData[i].Data[j].PanelPath); } } } /// <summary> /// json解析 /// </summary> private void ParseJsonLocalization() { //获取json文本对象 TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonLanguages) as TextAsset; //解析json jsonLocalizationData = JsonUtility.FromJson<JsonLocalizationModel>(assetText.text); for (int i = 0; i < jsonLocalizationData.AllData.Length; i++) { //新定义一个字典 Dictionary<string, string[]> crtPanelData = new Dictionary<string, string[]>(); //将场景ID和字典存入 jsonLocalizationDic.Add(i, crtPanelData); //将场景的全部资源路径保存 for (int j = 0; j < jsonLocalizationData.AllData[i].Data.Length; j++) { crtPanelData.Add(jsonLocalizationData.AllData[i].Data[j].ObjName, jsonLocalizationData.AllData[i].Data[j].TextLanguageText); } } } private void ParseJsonWidget() { TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonWidgetsPath) as TextAsset; widgetData = JsonUtility.FromJson<JsonWidgetModel>(assetText.text); for (int i = 0; i < widgetData.AllData.Length; i++) { //建立一个字典 Dictionary<string, string> crtDic = new Dictionary<string, string>(); //添加一个场景ID和一个字典 widgetDataDic.Add(i, crtDic); //遍历当前场景内的全部Panel路径数据 for (int j = 0; j < widgetData.AllData[i].Data.Length; j++) { //以PanelName为Key,以PanelPath为value进行存储 crtDic.Add(widgetData.AllData[i].Data[j].WidgetName, widgetData.AllData[i].Data[j].WidgetPath); } } } /// <summary> /// 获取资源路径 /// </summary> /// <param name="panelName"></param> /// <returns></returns> public string GetAssetPath(string panelName,int sceneID) { if (!jsonPanelDataDic.ContainsKey(sceneID)) { return null; } if (!jsonPanelDataDic[sceneID].ContainsKey(panelName)) { return null; } return jsonPanelDataDic[sceneID][panelName]; } /// <summary> /// 获取本地化语言数组 /// </summary> /// <param name="objName"></param> /// <param name="sceneID"></param> /// <returns></returns> public string[] GetLocalizationTextArray(string objName,int sceneID) { if (!jsonLocalizationDic.ContainsKey(sceneID)) { return null; } if (!jsonLocalizationDic[sceneID].ContainsKey(objName)) { return null; } return jsonLocalizationDic[sceneID][objName]; } public string GetWidgetPath(string widgetName, int sceneID) { if (!widgetDataDic.ContainsKey(sceneID)) return null; if (!widgetDataDic[sceneID].ContainsKey(widgetName)) return null; //若是都ID和Widget在字典中都存在,则直接返回 return widgetDataDic[sceneID][widgetName]; } } }
一样的,JsonPanelManager 这个脚本咱们也继承了单例,那么这个脚本主要是用来作什么的呢?很简单,就是解析json文件,将json文件中存储的资源路径都给拿出来,而后再利用上一个脚本将资源拿出来放进缓存(也就是咱们本身定义的字典里面,字典有多好用就不用我多说了吧)。至于json文件的解析,本篇博客也不赘述,都来看框架了,json解析这种东西应该没问题吧?那这个脚本中除了获取UI模块资源,还写了一个获取UI动态元件和本地化的json解析,暂时用不到,先不说。
好,那这个文件夹就只剩最后一个脚本了,SystemDefaine:
函数
using UnityEngine; public static class SystemDefaine { public const string JsonPanelsPath = "Configuration/UIPanelConfig"; public const string JsonWidgetsPath = "Configuration/UIWidgetConfig"; public const string JsonLanguages = "Configuration/LocalizationConfig"; public enum SceneID { Login = 0, FightScene = 1 } public static string[] UIWidgetsMark = new string[] { "_F" }; }
SystemDefaine这个脚本,单纯是用来保存常数的,注意,这是一个静态类,暂时咱们只用到了最上面的json文件路径的字符串常数。也没什么特殊的。其余的用到再说。学习
UIBases文件夹
UIBases文件夹下有以下脚本:
咱们先来看UIWidgetBase:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UIInterface; namespace UIFrame { public class UIWidgetBase : UIMono { //绑定所属的UI模块 private UIModuleBase crtModule; //初始化UI元件 public void UIWidgetInit(UIModuleBase uIModuleBase) { crtModule = uIModuleBase; UIManager.Instance.AddWidget(crtModule.name, name, this); } private void OnDisable() { UIManager.Instance.RemoveWidget(crtModule.name, name); } } }
UIWidgetBase 是一个经过代码添加到全部以“_F”结尾的UI元件身上的脚本组件。他继承了UIMono,UIMono则实现了不少关于UI组件操做的接口,详情本身去看看UIInterface文件夹,由于过于简单,因此博主不作讲解。
咱们再来看UIModuleBase:
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { //该脚本挂载在每一个UI模块上,并确保拥有CanvasGroup组件 [RequireComponent(typeof(CanvasGroup))] public class UIModuleBase : MonoBehaviour { protected CanvasGroup canvasGroup; //获取全部的子对象 private Transform[] allChild; protected virtual void Awake() { canvasGroup = GetComponent<CanvasGroup>(); //修改对象名称(将生成的对象名称后面的(clone)去掉) gameObject.name = gameObject.name.Remove(gameObject.name.Length - "(clone)".Length); allChild = transform.GetComponentsInChildren<Transform>(); AddUIWidgetBehaviour(); } //绑定该模块对应的Controller protected void BindController(UIControllerBase controller) { controller.ControllerInit(this); } /// <summary> /// 找出须要添加widgetbase的脚本 /// </summary> private void AddUIWidgetBehaviour() { for (int i = 0; i < allChild.Length; i++) { for (int j = 0; j < SystemDefaine.UIWidgetsMark.Length; j++) { //全部子对象中名称以规定字符串结尾的都添加上一个UIWidgetBase脚本。 if (allChild[i].name.EndsWith(SystemDefaine.UIWidgetsMark[j])) { AddComponetForWidget(i); } } } } /// <summary> /// 给对象添加widgetbase组件 /// </summary> /// <param name="index"></param> protected virtual void AddComponetForWidget(int index) { UIWidgetBase crtbase = allChild[index].gameObject.AddComponent<UIWidgetBase>(); crtbase.UIWidgetInit(this); } //经过UI元件名称获取该模块中绑定的UI元件 public UIWidgetBase GetWidget(string widgetName) { return UIManager.Instance.GetWidget(name, widgetName); } //如下是用以模态处理的四个操做,你能够在重写时加入想要的模态处理效果,或者添加一些动画之类的效果,简单易懂,不作赘述 public virtual void OnEnter() { canvasGroup.blocksRaycasts = true; //LocalizationManager.Instance.LocalizationInit(); } public virtual void OnPurse() { canvasGroup.blocksRaycasts = false; } public virtual void OnResume() { canvasGroup.blocksRaycasts = true; } public virtual void OnExit() { canvasGroup.blocksRaycasts = false; } } }
UIModuleBase这个脚本是挂载在全部UI模块上的。可是,不是直接挂载,而是每一个模块本身写一个脚本继承这个脚本以后挂载。而且须要重写这个脚本的awake方法。具体的解释能够仔细看看代码以及注释,不难理解。值得一提的是,必定要记得绑定相对应的Controller,否则没法实现相关功能。
那么最后咱们来讲说UIControllerBase:
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class UIControllerBase { //绑定相应的UIModuleBase protected UIModuleBase uiModuleBase; //初始化控制器 public void ControllerInit(UIModuleBase uiModuleBase) { this.uiModuleBase = uiModuleBase; ControllerStart(); } protected virtual void ControllerStart() { } } }
UIControllerBase跟UIModuleBase 同样,每一个UI模块都须要写一个自身的控制器继承这个UIControllerBase,而后不须要挂载,由于他跟UIModuleBase 绑定在一块儿,能够直接调用。具体用法咱们后面说。咱们先接着看框架。
Managers文件夹
Managers文件夹下有以下脚本:
这两个脚本才是咱们框架的重中之重,或者说UIManager这个脚本才是核心。
话很少说,咱们直接看这个脚本:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace UIFrame { public class UIManager : Singleton<UIManager> { private UIManager() { canvas = GameObject.Find("Canvas").transform; uiModuleBases = new Dictionary<UIType, UIModuleBase>(); uiModuleStack = new Stack<UIModuleBase>(); uiWidgetBases = new Dictionary<string, Dictionary<string, UIWidgetBase>>(); uiModuleList = new List<UIModuleBase>(); } /// <summary> /// ui模块的栈 /// </summary> Stack<UIModuleBase> uiModuleStack; /// <summary> /// ui模块的列表存储 /// </summary> List<UIModuleBase> uiModuleList; /// <summary> /// 管理全部的UI模块 /// </summary> Dictionary<UIType, UIModuleBase> uiModuleBases; /// <summary> /// 管理全部的元件 /// </summary> Dictionary<string, Dictionary<string, UIWidgetBase>> uiWidgetBases; /// <summary> /// 画布 /// </summary> private Transform canvas; public UIModuleBase GetUIModuleByName(string panelName) { UIType type = UITypeManager.Instance.GetUiType(panelName, 0); return GetUIModule(type); } /// <summary> /// 获取ui模块 /// </summary> /// <param name="type"></param> /// <returns></returns> public UIModuleBase GetUIModule(UIType type) { UIModuleBase crtUIMdule = null; if (!uiModuleBases.TryGetValue(type,out crtUIMdule)) { crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject); uiModuleBases.Add(type, crtUIMdule); } else if(crtUIMdule == null) { crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject); uiModuleBases[type] = crtUIMdule; } return crtUIMdule; } /// <summary> /// 生成ui模块对象 /// </summary> /// <param name="prefab"></param> /// <returns></returns> private UIModuleBase InstantiateUIModule(GameObject prefab) { GameObject obj = GameObject.Instantiate(prefab); obj.transform.SetParent(canvas, false); return obj.GetComponent<UIModuleBase>(); } /// <summary> /// 压栈 /// </summary> /// <param name="panelName">模块名称</param> /// <param name="sceneID">场景ID</param> public void PushUI(string panelName,int sceneID) { //定义一个uitype UIType type = UITypeManager.Instance.GetUiType(panelName,sceneID); //获取模块 UIModuleBase crtbase = GetUIModule(type); //若是栈中已有元素 if (uiModuleStack.Count > 0) { //当前模块暂停使用 uiModuleStack.Peek().OnPurse(); } //新模块压栈 uiModuleStack.Push(crtbase); //新模块开启 crtbase.OnEnter(); } /// <summary> /// 展现UI /// </summary> /// <param name="panelName"></param> /// <param name="sceneID"></param> public void ShowUI(string panelName,int sceneID) { //定义一个uitype UIType type = UITypeManager.Instance.GetUiType(panelName, sceneID); //获取模块 UIModuleBase crtbase = GetUIModule(type); //若是栈中已有元素 if (!uiModuleList.Contains(crtbase)) { //当前模块暂停使用 uiModuleList.Add(crtbase); } //新模块开启 crtbase.OnEnter(); } /// <summary> /// 出栈 /// </summary> public void PopUI() { if (uiModuleStack.Count > 0) { uiModuleStack.Pop().OnExit(); } if (uiModuleStack.Count > 0) { uiModuleStack.Peek().OnResume(); } } /// <summary> /// 注册UI模块元件 /// </summary> /// <param name="moduleName"></param> private void RegisterUIModuleWidgets(string moduleName) { if (!uiWidgetBases.ContainsKey(moduleName)) { uiWidgetBases.Add(moduleName, new Dictionary<string, UIWidgetBase>()); } } /// <summary> /// 注销UI模块元件 /// </summary> /// <param name="moduleName"></param> public void UnRegisterUIModuleWidgets(string moduleName) { if (uiWidgetBases.ContainsKey(moduleName)) { uiWidgetBases.Remove(moduleName); } } /// <summary> /// 添加元件 /// </summary> /// <param name="moduleName">模块名</param> /// <param name="widgetName">元件名</param> /// <param name="widget">元件</param> public void AddWidget(string moduleName, string widgetName, UIWidgetBase widget) { RegisterUIModuleWidgets(moduleName); if (!uiWidgetBases[moduleName].ContainsKey(widgetName)) { uiWidgetBases[moduleName].Add(widgetName, widget); } } /// <summary> /// 移除元件 /// </summary> /// <param name="moduleName"></param> /// <param name="widgetName"></param> public void RemoveWidget(string moduleName, string widgetName) { if (!uiWidgetBases.ContainsKey(moduleName)) { return; } if (!uiWidgetBases[moduleName].ContainsKey(widgetName)) { return; } uiWidgetBases[moduleName].Remove(widgetName); } /// <summary> /// 获取元件 /// </summary> /// <param name="moduleName"></param> /// <param name="widgetName"></param> /// <returns></returns> public UIWidgetBase GetWidget(string moduleName, string widgetName) { //若是该模块未注册,注册 if (!uiWidgetBases.ContainsKey(moduleName)) { RegisterUIModuleWidgets(moduleName); } UIWidgetBase widget = null; //尝试获取该模块此元件并返回,没有返回null uiWidgetBases[moduleName].TryGetValue(widgetName, out widget); return widget; } public GameObject CreateDynamicWidget(string widgetName) { string widgetPath = JsonPanelManager.Instance.GetWidgetPath(widgetName, 0); GameObject prefab = AssetsManager.Instance.GetAsset(widgetPath) as GameObject; return GameObject.Instantiate(prefab); } public GameObject CreateDynamicWidget(string widgetName, Transform parent, bool worldPosStays) { GameObject obj = CreateDynamicWidget(widgetName); obj.transform.SetParent(parent, worldPosStays); return obj; } } }
能够这么说,你看懂了这个脚本,那么基本上这个框架你就看懂了六成以上。首先,这个脚本也是实现的单例。而后你会发现,咱们定义了不少容器,包括栈,集合,字典等等,个人注释也写得很清楚他们各自负责的是什么。读懂实际上是不难的,也没有什么复杂的语法,主要就是各个脚本之间相互跳转可能会有一点绕。但这些看起来麻烦的操做都是为了一件事:解耦合。不用我多说,你们也都知道解耦合对一个程序来讲有多重要。这个脚本我能说的很少,由于各个模块的跳转须要你本身在编译器上读代码的时候本身跳转看起来更方便。
下面我写一个简单的框架使用过程,但愿能帮你更好的理解这个框架。
使用该框架
新建两个文件夹:
方便咱们管理个人脚本,这两个文件夹名称也很能说明问题,天然一个是放模块脚本,一个是放控制器脚本的。
我写了一个MainModule和一个MainController :
using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; public class MainModule : UIModuleBase { private MainController controller; protected override void Awake() { base.Awake(); controller = new MainController(); BindController(controller); } }
MainModule继承了UIModuleBase ,并挂载在UI模块mainPanel上面。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; using Photon.Pun; using Hashtable = ExitGames.Client.Photon.Hashtable; public class MainController : UIControllerBase { protected override void ControllerStart() { base.ControllerStart(); MainModuleInit(); BindEvents(); } void MainModuleInit() { uiModuleBase.GetWidget("PlayerNameInputField_F").SetInputFieldText("Player" + Random.Range(101, 999)); MonoHelper.Instance.InvokeRepeat(() => { uiModuleBase.GetWidget("NetworkingState_F").SetTextText(PhotonNetwork.NetworkClientState.ToString()); }, 0, () => { return false; }); } void SetPlayerInfo() { PhotonNetwork.LocalPlayer.NickName = uiModuleBase.GetWidget("PlayerNameInputField_F").GetInputFieldText(); int teamIndex = uiModuleBase.GetWidget("BattleArrayDropdown_F").GetDropDownValue(); Hashtable table = new Hashtable(); table.Add(GameConst.TEAMINDEX, teamIndex); PhotonNetwork.LocalPlayer.SetCustomProperties(table); } void BindEvents() { uiModuleBase.GetWidget("BattleArrayDropdown_F").OnDropDownValueChange(OnDropDownValueChange); uiModuleBase.GetWidget("CreateRoomButton_F").AddOnClickListener(() => { SetPlayerInfo(); UIManager.Instance.PushUI("CreateRoomModule",0); }); uiModuleBase.GetWidget("RandomJoinRoomButton_F").AddOnClickListener(() => { SetPlayerInfo(); Hashtable hashtable = new Hashtable(); hashtable.Add("Password", "NULL"); PhotonNetwork.JoinRandomRoom(hashtable,2); //UIManager.Instance.PushUI("RoomModule", 0); }); uiModuleBase.GetWidget("RandomLobbyButton_F").AddOnClickListener(() => { SetPlayerInfo(); PhotonNetwork.JoinLobby(); //UIManager.Instance.PushUI("LobbyModule", 0); }); } void OnDropDownValueChange(int value) { if (value == 0) { uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.red); } else { uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.blue); } } }
MainController 继承了UIControllerBase ,并与MainModule 绑定在一块儿。经过uiModuleBase的GetWidget(string widgetName)方法能够拿到相应的UI元件,并经过UIMono中实现的接口方法完成所要实现的需求。这里作的是一个网络对战游戏的主界面,因此实现的都是网络相关功能。也只是一些简单的添加点击实现,获取UI输入框中的内容,更改UI输入框中的内容,都是能够一句话就解决,很是方便,也很是酷。
固然,咱们还须要在游戏一开始运行的时候,先自动push出咱们的主页面,因此还须要一个类来启动框架。
我写了一个Facade:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; public class Facade : MonoBehaviour { private void Start() { UIManager.Instance.PushUI("MainModule",0); } }
这个脚本很简单,只是将主模块在游戏开始运行时push出来。你也能够有本身的想法。
总结
若是你看到了这里,那么恭喜你,你已经对UI框架有了一个最最最基础的认知。这是一个很简单的UI框架,代码量总共也没多少,真正的框架可比这个复杂的多,也不是一篇博客就能说得清的,若是想深刻学习,能够去找找大牛写的框架,本博客只作入门用,甚至都算不上。但愿能对小白有些帮助。一块儿加油吧!