6. Unity3d Lua框架 - xLua - UIManager UI管理系统:提供UI操作、UI层级、UI消息、UI资源加载、UI调度、UI缓存等管理

  1. UIManager.lua

– UI管理系统:提供UI操作、UI层级、UI消息、UI资源加载、UI调度、UI缓存等管理
– 注意:
– 1、Window包括:Model、Ctrl、View、和Active状态等构成的一个整体概念
– 2、所有带Window接口的都是操作整个窗口,如CloseWindow以后:整个窗口将不再活动
– 3、所有带View接口的都是操作视图层展示,如CloseView以后:View、Model依然活跃,只是看不见,可看做切入了后台
– 4、如果只是要监听数据,可以创建不带View、Ctrl的后台窗口,配置为nil,比如多窗口需要共享某控制model(配置为后台窗口)
– 5、可将UIManager看做一个挂载在UIRoot上的不完全UI组件,但是它是Singleton,不使用多重继承,UI组件特性隐式实现

支付宝捐赠

打赏红包

--[[
-- UI管理系统:提供UI操作、UI层级、UI消息、UI资源加载、UI调度、UI缓存等管理
-- 注意:
-- 1、Window包括:Model、Ctrl、View、和Active状态等构成的一个整体概念
-- 2、所有带Window接口的都是操作整个窗口,如CloseWindow以后:整个窗口将不再活动
-- 3、所有带View接口的都是操作视图层展示,如CloseView以后:View、Model依然活跃,只是看不见,可看做切入了后台
-- 4、如果只是要监听数据,可以创建不带View、Ctrl的后台窗口,配置为nil,比如多窗口需要共享某控制model(配置为后台窗口)
-- 5、可将UIManager看做一个挂载在UIRoot上的不完全UI组件,但是它是Singleton,不使用多重继承,UI组件特性隐式实现
--]]

local Messenger = require "Framework.Common.Messenger"
local UIManager = BaseClass("UIManager", Singleton)

-- UIRoot路径
local UIRootPath = "UIRoot"
-- EventSystem路径
local EventSystemPath = "EventSystem"
-- UICamera路径
local UICameraPath = UIRootPath.."/UICamera"
-- 分辨率
local Resolution = Vector2.New(1024, 768)
-- 窗口最大可使用的相对order_in_layer
local MaxOderPerWindow = 10
-- cs Tip
local UINoticeTip = CS.UINoticeTip.Instance

-- 构造函数
local function __init(self)
	-- 成员变量
	-- 消息中心
	self.ui_message_center = Messenger.New()
	-- 所有存活的窗体
	self.windows = {}
	-- 所有可用的层级
	self.layers = {}
	-- 保持Model
	self.keep_model = {}
	-- 窗口记录队列
	self.__window_stack = {}
	-- 是否启用记录
	self.__enable_record = true
	
	-- 初始化组件
	self.gameObject = CS.UnityEngine.GameObject.Find(UIRootPath)
	self.transform = self.gameObject.transform
	self.camera_go = CS.UnityEngine.GameObject.Find(UICameraPath)
	self.UICamera = self.camera_go:GetComponent(typeof(CS.UnityEngine.Camera))
	self.Resolution = Resolution
	self.MaxOderPerWindow = MaxOderPerWindow
	CS.UnityEngine.Object.DontDestroyOnLoad(self.gameObject)
	local event_system = CS.UnityEngine.GameObject.Find(EventSystemPath)
	CS.UnityEngine.Object.DontDestroyOnLoad(event_system)
	assert(not IsNull(self.transform))
	assert(not IsNull(self.UICamera))
	
	-- 初始化层级
	local layers = table.choose(Config.Debug and getmetatable(UILayers) or UILayers, function(k, v)
		return type(v) == "table" and v.OrderInLayer ~= nil and v.Name ~= nil and type(v.Name) == "string" and #v.Name > 0
	end)
	table.walksort(layers, function(lkey, rkey)
		return layers[lkey].OrderInLayer < layers[rkey].OrderInLayer
	end, function(index, layer)
		assert(self.layers[layer.Name] == nil, "Aready exist layer : "..layer.Name)
		local go = CS.UnityEngine.GameObject(layer.Name)
		local trans = go.transform
		trans:SetParent(self.transform)
		local new_layer = UILayer.New(self, layer.Name)
		new_layer:OnCreate(layer)
		self.layers[layer.Name] = new_layer
	end)
end

-- 注册消息
local function AddListener(self, e_type, e_listener)
	self.ui_message_center:AddListener(e_type, e_listener)
end

-- 发送消息
local function Broadcast(self, e_type, ...)
	self.ui_message_center:Broadcast(e_type, ...)
end

-- 注销消息
local function RemoveListener(self, e_type, e_listener)
	self.ui_message_center:RemoveListener(e_type, e_listener)
end

-- 获取窗口
local function GetWindow(self, ui_name, active, view_active)
	local target = self.windows[ui_name]
	if target == nil then
		return nil
	end
	if active ~= nil and target.Active ~= active then
		return nil
	end
	if view_active ~= nil and target.View:GetActive() ~= view_active then
		return nil
	end
	return target
end

-- 初始化窗口
local function InitWindow(self, ui_name, window)
	local config = UIConfig[ui_name]
	assert(config, "No window named : "..ui_name..".You should add it to UIConfig first!")
	
	local layer = self.layers[config.Layer.Name]
	assert(layer, "No layer named : "..config.Layer.Name..".You should create it first!")
	
	window.Name = ui_name
	if self.keep_model[ui_name] then
		window.Model = self.keep_model[ui_name]
	elseif config.Model then
		window.Model = config.Model.New(ui_name)
	end
	if config.Ctrl then
		window.Ctrl = config.Ctrl.New(window.Model)
	end
	if config.View then
		window.View = config.View.New(layer, window.Name, window.Model, window.Ctrl)
	end
	window.Active = false
	window.Layer = layer
	window.PrefabPath = config.PrefabPath
	
	self:Broadcast(UIMessageNames.UIFRAME_ON_WINDOW_CREATE, window)
	return window
end

-- 激活窗口
local function ActivateWindow(self, target, ...)
	assert(target)
	assert(target.IsLoading == false, "You can only activate window after prefab locaded!")
	target.Model:Activate(...)
	target.View:SetActive(true)
	self:Broadcast(UIMessageNames.UIFRAME_ON_WINDOW_OPEN, target)
end

-- 反激活窗口
local function Deactivate(self, target)
	target.Model:Deactivate()
	target.View:SetActive(false)
	self:Broadcast(UIMessageNames.UIFRAME_ON_WINDOW_CLOSE, target)
end

-- 打开窗口:私有,必要时准备资源
local function InnerOpenWindow(self, target, ...)
	assert(target)
	assert(target.Model)
	assert(target.Ctrl)
	assert(target.View)
	assert(target.Active == false, "You should close window before open again!")
	
	target.Active = true
	local has_view = target.View ~= UIBaseView
	local has_prefab_res = target.PrefabPath and #target.PrefabPath > 0
	local has_loaded = not IsNull(target.View.gameObject)
	local need_load = has_view and has_prefab_res and not has_loaded
	if not need_load then
		ActivateWindow(self, target, ...)
	elseif not target.IsLoading then
		target.IsLoading = true
		local params = SafePack(...)
		GameObjectPool:GetInstance():GetGameObjectAsync(target.PrefabPath, function(go)
			if IsNull(go) then
				return
			end
			
			local trans = go.transform
			trans:SetParent(target.Layer.transform)
			trans.name = target.Name
			
			target.IsLoading = false
			target.View:OnCreate()
			if target.Active then
				ActivateWindow(self, target, SafeUnpack(params))
			end
		end)
	end
end

-- 关闭窗口:私有
local function InnerCloseWindow(self, target)
	assert(target)
	assert(target.Model)
	assert(target.Ctrl)
	assert(target.View)
	if target.Active then
		Deactivate(self, target)
		target.Active = false
	end
end

-- 打开窗口:公有
local function OpenWindow(self, ui_name, ...)
	local target = self:GetWindow(ui_name)
	if not target then
		local window = UIWindow.New()
		self.windows[ui_name] = window
		target = InitWindow(self, ui_name, window)
	end
	
	-- 先关闭
	InnerCloseWindow(self, target)
	InnerOpenWindow(self, target, ...)
	
	-- 窗口记录
	local layer = UIConfig[ui_name].Layer
	if layer == UILayers.BackgroudLayer then
		local bg_index = self:GetLastBgWindowIndexInWindowStack()
		if bg_index == -1 or self.__window_stack[bg_index] ~= target.Name then
			self:AddToWindowStack(target.Name)
		else
			self:PopWindowStack()
		end
	elseif layer == UILayers.NormalLayer then
		self:AddToWindowStack(target.Name)
	end
end

-- 关闭窗口:公有
local function CloseWindow(self, ui_name)
	local target = self:GetWindow(ui_name, true)
	if not target then
		return
	end
	
	InnerCloseWindow(self, target)
	
	-- 窗口记录
	local layer = UIConfig[ui_name].Layer
	if layer == UILayers.BackgroudLayer then
		if target.Name == self.__window_stack[table.count(self.__window_stack)] then
			self:RemoveFormWindowStack(target.Name, true)
			--self:PopWindowStack()
		else
			self:RemoveFormWindowStack(target.Name, true)
		end
	elseif layer == UILayers.NormalLayer then
		self:RemoveFormWindowStack(target.Name, true)
	end
end

-- 关闭层级所有窗口
local function CloseWindowByLayer(self, layer)
	for _,v in pairs(self.windows) do
		if v.Layer:GetName() == layer.Name then
			InnerCloseWindow(self, v)
		end
	end
end

-- 关闭其它层级窗口
local function CloseWindowExceptLayer(self, layer)
	for _,v in pairs(self.windows) do
		if v.Layer:GetName() ~= layer.Name then
			InnerCloseWindow(self, v)
		end
	end
end

-- 关闭所有窗口
local function CloseAllWindows(self)
	for _,v in pairs(self.windows) do
		InnerCloseWindow(self, v)
	end
end

-- 展示窗口
local function OpenView(self, ui_name, ...)
	local target = self:GetWindow(ui_name)
	assert(target, "Try to show a window that does not exist: "..ui_name)
	if not target.View:GetActive() then
		target.View:SetActive(true)
	end
end

-- 隐藏窗口
local function CloseView(self, ui_name)
	local target = self:GetWindow(ui_name)
	assert(target, "Try to hide a window that does not exist: "..ui_name)
	if target.View:GetActive() then
		target.View:SetActive(false)
	end
end

local function InnerDelete(plugin)
	if plugin.__ctype == ClassType.instance then
		plugin:Delete()
	end
end

local function InnerDestroyWindow(self, ui_name, target, include_keep_model)
	self:Broadcast(UIMessageNames.UIFRAME_ON_WINDOW_DESTROY, target)
	-- 说明:一律缓存,如果要真的清理,那是清理缓存时需要管理的功能
	GameObjectPool:GetInstance():RecycleGameObject(self.windows[ui_name].PrefabPath, target.View.gameObject)
	if include_keep_model then
		self.keep_model[ui_name] = nil
		InnerDelete(target.Model)
	elseif not self.keep_model[ui_name] then
		InnerDelete(target.Model)
	end
	InnerDelete(target.Ctrl)
	InnerDelete(target.View)
	self.windows[ui_name] = nil
end

-- 销毁窗口
local function DestroyWindow(self, ui_name, include_keep_model)
	local target = self:GetWindow(ui_name)
	if not target then
		return
	end
	
	InnerCloseWindow(self, target)
	InnerDestroyWindow(self, ui_name, target, include_keep_model)
end

-- 销毁层级所有窗口
local function DestroyWindowByLayer(self, layer, include_keep_model)
	for k,v in pairs(self.windows) do
		if v.Layer:GetName() == layer.Name then
			InnerCloseWindow(self, v)
			InnerDestroyWindow(self, k, v, include_keep_model)
		end
	end
end

-- 销毁其它层级窗口
local function DestroyWindowExceptLayer(self, layer, include_keep_model)
	for k,v in pairs(self.windows) do
		if v.Layer:GetName() ~= layer.Name then
			InnerCloseWindow(self, v)
			InnerDestroyWindow(self, k, v, include_keep_model)
		end
	end
end

-- 销毁所有窗口
local function DestroyAllWindow(self, include_keep_model)
	for k,v in pairs(self.windows) do
		InnerCloseWindow(self, v)
		InnerDestroyWindow(self, k, v, include_keep_model)
	end
end

-- 设置是否保持Model
local function SetKeepModel(self, ui_name, keep)
	local target = self:GetWindow(ui_name)
	assert(target, "Try to keep a model that window does not exist: "..ui_name)
	if keep then
		self.keep_model[target.Name] = target.Model
	else
		self.keep_model[target.Name] = nil
	end
end

-- 获取保持的Model
local function GetKeepModel(self, ui_name)
	return self.keep_model[ui_name]
end

-- 加入窗口记录栈
local function AddToWindowStack(self, ui_name)
	if not self.__enable_record then
		return
	end
	
	table.insert(self.__window_stack, ui_name)
	-- 保持Model
	self:SetKeepModel(ui_name, true)
end

-- 从窗口记录栈中移除
local function RemoveFormWindowStack(self, ui_name, only_check_top)
	if not self.__enable_record then
		return
	end
	
	local index = table.indexof(self.__window_stack, ui_name)
	if not index then
		return
	end
	if only_check_top and index ~= table.count(self.__window_stack) then
		return
	end
	
	local ui_name = table.remove(self.__window_stack, index)
	-- 取消Model保持
	self:SetKeepModel(ui_name, false)
end

-- 获取记录栈
local function GetWindowStack(self)
	return self.__window_stack
end

-- 清空记录栈
local function ClearWindowStack(self)
	self.__window_stack = {}
end

-- 获取最后添加的一个背景窗口索引
local function GetLastBgWindowIndexInWindowStack(self)
	local bg_index = -1
	for i = 1, table.count(self.__window_stack) do
		local ui_name = self.__window_stack[i]
		if UIConfig[ui_name].Layer == UILayers.BackgroudLayer then
			bg_index = i
		end
	end
	return bg_index
end

-- 弹出栈
-- 注意:从上一个记录的背景UI开始弹出之后所有被记录的窗口
local function PopWindowStack(self)
	local bg_index = self:GetLastBgWindowIndexInWindowStack()
	if bg_index == -1 then
		-- 没找到背景UI
		if table.count(self.__window_stack) > 0 then
			error("There is something wrong!")
		end
		return
	end
	
	self.__enable_record = false
	local end_index = table.count(self.__window_stack)
	for i = bg_index + 1, end_index  do
		local ui_name = self.__window_stack[i]
		UIManager:GetInstance():OpenWindow(ui_name)
	end
	self.__enable_record = true
end

-- 展示Tip:单按钮
local function OpenOneButtonTip(self, title, content, btnText, callback)
	local ui_name = UIWindowNames.UINoticeTip
	local cs_func = UINoticeTip.ShowOneButtonTip
	self:OpenWindow(ui_name, cs_func, title, content, btnText, callback)
end

-- 展示Tip:双按钮
local function OpenTwoButtonTip(self, title, content, btnText1, btnText2, callback1, callback2)
	local ui_name = UIWindowNames.UINoticeTip
	local cs_func = UINoticeTip.ShowTwoButtonTip
	self:OpenWindow(ui_name, cs_func, title, content, btnText1, btnText2, callback1, callback2)
end

-- 展示Tip:三按钮
local function OpenThreeButtonTip(self, title, content, btnText1, btnText2, btnText3, callback1, callback2, callback3)
	local ui_name = UIWindowNames.UINoticeTip
	local cs_func = UINoticeTip.ShowThreeButtonTip
	self:OpenWindow(ui_name, cs_func, title, content, btnText1, btnText2, btnText3, callback1, callback2, callback3)
end

-- 隐藏Tip
local function CloseTip(self)
	local ui_name = UIWindowNames.UINoticeTip
	self:CloseWindow(ui_name)
end

-- 等待View层窗口创建完毕(资源加载完毕):用于协程
local function WaitForViewCreated(self, ui_name)
	local window = self:GetWindow(ui_name, true)
	assert(window ~= nil, "Try to wait for a not opened window : "..ui_name)
	if IsNull(window.View.gameObject) then
		window.View:WaitForCreated()
	end
	return window
end

-- 等待Tip响应:用于协程,返回点击序号,-1表示无响应且窗口被异常关闭
local function WaitForTipResponse(self)
	local ui_name = UIWindowNames.UINoticeTip
	local window = self:WaitForViewCreated(ui_name)
	return window.Model:WaitForResponse()
end

-- 析构函数
local function __delete(self)
	self.ui_message_center = nil
	self.windows = nil
	self.layers = nil
	self.keep_model = nil
end

UIManager.__init = __init
UIManager.AddListener = AddListener
UIManager.Broadcast = Broadcast
UIManager.RemoveListener = RemoveListener
UIManager.GetWindow = GetWindow
UIManager.OpenWindow = OpenWindow
UIManager.CloseWindow = CloseWindow
UIManager.CloseWindowByLayer = CloseWindowByLayer
UIManager.CloseWindowExceptLayer = CloseWindowExceptLayer
UIManager.CloseAllWindows = CloseAllWindows
UIManager.OpenView = OpenView
UIManager.CloseView = CloseView
UIManager.DestroyWindow = DestroyWindow
UIManager.DestroyWindowByLayer = DestroyWindowByLayer
UIManager.DestroyWindowExceptLayer = DestroyWindowExceptLayer
UIManager.DestroyAllWindow = DestroyAllWindow
UIManager.SetKeepModel = SetKeepModel
UIManager.GetKeepModel = GetKeepModel
UIManager.AddToWindowStack = AddToWindowStack
UIManager.RemoveFormWindowStack = RemoveFormWindowStack
UIManager.GetLastBgWindowIndexInWindowStack = GetLastBgWindowIndexInWindowStack
UIManager.GetWindowStack = GetWindowStack
UIManager.ClearWindowStack = ClearWindowStack
UIManager.PopWindowStack = PopWindowStack
UIManager.OpenOneButtonTip = OpenOneButtonTip
UIManager.OpenTwoButtonTip = OpenTwoButtonTip
UIManager.OpenThreeButtonTip = OpenThreeButtonTip
UIManager.CloseTip = CloseTip
UIManager.WaitForViewCreated = WaitForViewCreated
UIManager.WaitForTipResponse = WaitForTipResponse
UIManager.GetTipLastClickIndex = GetTipLastClickIndex
UIManager.__delete = __delete

return UIManager;

支付宝捐赠

打赏红包

  1. UIWindow.lua
--[[
-- UIWindow数据,用以表示一个窗口
-- 注意:
-- 1、窗口名字必须和预设名字一致
--]]

local UIWindow = {
	-- 窗口名字
	Name = "Background",
	-- Layer层级
	Layer = UILayers.NormalLayer,
	-- Model实例
	Model = UIBaseModel,
	-- Ctrl实例
	Ctrl = UIBaseCtrl,
	-- View实例
	View = UIBaseView,
	-- 是否激活
	Active = false,
	-- 预设路径
	PrefabPath = "",
	-- 是否正在加载
	IsLoading = false,
}
	
return DataClass("UIWindow", UIWindow)
  1. UILayers.lua
--[[
-- UILayers配置
--]]

local UILayers = {
	-- 场景UI,如:点击建筑查看建筑信息---一般置于场景之上,界面UI之下
	SceneLayer = {
		Name = "SceneLayer",
		PlaneDistance = 1000,
		OrderInLayer = 0,
	},
	-- 背景UI,如:主界面---一般情况下用户不能主动关闭,永远处于其它UI的最底层
	BackgroudLayer = {
		Name = "BackgroudLayer",
		PlaneDistance = 900,
		OrderInLayer = 1000,
	},
	-- 普通UI,一级、二级、三级等窗口---一般由用户点击打开的多级窗口
	NormalLayer = {
		Name = "NormalLayer",
		PlaneDistance = 800,
		OrderInLayer = 2000,
	},
	-- 信息UI---如:跑马灯、广播等---一般永远置于用户打开窗口顶层
	InfoLayer = {
		Name = "InfoLayer",
		PlaneDistance = 700,
		OrderInLayer = 3000,
	},
	-- 提示UI,如:错误弹窗,网络连接弹窗等
	TipLayer = {
		Name = "TipLayer",
		PlaneDistance = 600,
		OrderInLayer = 4000,
	},
	-- 顶层UI,如:场景加载
	TopLayer = {
		Name = "TopLayer",
		PlaneDistance = 500,
		OrderInLayer = 5000,
	},
}
	
return ConstClass("UILayers", UILayers)
  1. UIMessageNames.lua
--[[
-- UI消息定义,手动添加
-- 定义格式:模块名_ON_事件描述 = "驼峰式消息名"
-- 注意:
-- 1、这类型的消息只在UI模块中流通,View层只关注这里的消息,由Model层发送
-- 2、如果窗口足够简单,那每次数据变化时发送OnRefresh消息就可以了,View层进行整体刷新,以避免消息臃肿
--]]

local UIMessageNames = {
	-- 框架消息
	UIFRAME_ON_WINDOW_CREATE = "UIFrameOnWindowCreeate",
	UIFRAME_ON_WINDOW_OPEN = "UIFrameOnWindowOpen",
	UIFRAME_ON_WINDOW_CLOSE = "UIFrameOnWindowClose",
	UIFRAME_ON_WINDOW_DESTROY = "UIFrameOnWindowDestroy",
	
	-- 模块消息添加到下面
	-- UILogin模块
	UILOGIN_ON_SELECTED_SVR_CHG = "UILoginOnSelectedSvrChg",
}

return ConstClass("UIMessageNames", UIMessageNames)
  1. UIUtil.lua
--[[
-- UI工具类
--]]

local UIUtil = {}

local function GetChild(trans, index)
	return trans:GetChild(index)
end

-- 注意:根节点不能是隐藏状态,否则路径将找不到
local function FindComponent(trans, ctype, path)
	assert(trans ~= nil)
	assert(ctype ~= nil)
	
	local targetTrans = trans
	if path ~= nil and type(path) == "string" and #path > 0 then
		targetTrans = trans:Find(path)
	end
	if targetTrans == nil then
		return nil
	end
	local cmp = targetTrans:GetComponent(ctype)
	if cmp ~= nil then
		return cmp
	end
	return targetTrans:GetComponentInChildren(ctype)
end

local function FindTrans(trans, path)
	return trans:Find(path)
end

local function FindText(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.Text), path)
end

local function FindImage(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.Image), path)
end

local function FindButton(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.Button), path)
end

local function FindInput(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.InputField), path)
end

local function FindSlider(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.Slider), path)
end

local function FindScrollRect(trans, path)
	return FindComponent(trans, typeof(CS.UnityEngine.UI.ScrollRect), path)
end

-- 获取直属画布
local function GetCanvas(ui_component)
	-- 初始化直属画布
	local canvas = nil
	if ui_component._class_type == UILayer then
		canvas = ui_component
	else
		local now_holder = ui_component.holder
		while now_holder ~= nil do	
			local var = ui_component:GetComponents(UICanvas)
			if table.count(var) > 0 then
				assert(table.count(var) == 1)
				canvas = var[1]
				break
			end
			now_holder = now_holder.holder
		end
	end
	assert(canvas ~= nil)
	return canvas
end

UIUtil.GetChild = GetChild
UIUtil.FindComponent = FindComponent
UIUtil.FindTrans = FindTrans
UIUtil.FindText = FindText
UIUtil.FindImage = FindImage
UIUtil.FindButton = FindButton
UIUtil.FindInput = FindInput
UIUtil.FindSlider = FindSlider
UIUtil.FindScrollRect = FindScrollRect

return ConstClass("UIUtil", UIUtil)

支付宝捐赠

打赏红包

你可能感兴趣的:(Lua)