-- 部分函数和变量名前加了下划线主要是避免被子类不经意间重写,同时也不应该直接调用这些属性或者方法 local BF_UI_HELPER = GConst.TYPEOF_UNITY_CLASS.BF_UI_HELPER local BF_BASE_SORTING_ORDER_HELPER = GConst.TYPEOF_UNITY_CLASS.BF_BASE_SORTING_ORDER_HELPER local BaseUI = { _baseAlreadyClosed = false, _baseUIOrder = 0 } function BaseUI:ctor() end -- 需要在打开界面之前预先加载的资源列表,例如 -- { -- [GConst.TYPEOF_UNITY_CLASS.GAME_OBJECT] = { -- "xxx1", "xxx2" -- } -- } function BaseUI:getPreLoadList() return nil end function BaseUI:showCommonBG() return true end -- 界面prefab路径 function BaseUI:getPrefabPath() return "" end -- 关闭界面 function BaseUI:closeUI() UIManager:closeUI(self) end function BaseUI:addLoadUICompleteListener(callback) if self._baseLoadComplete then if callback then callback() end else self._onLoadUICompleteCallback = callback end end function BaseUI:getIsLoadedComplete() return self._baseLoadComplete end function BaseUI:addEnterAniCompleteListener(callback) self._onEnterAniCompleteCallback = callback end function BaseUI:addExitAniCompleteListener(callback) self._onExitAniCompleteCallback = callback end -- 界面进入动画播放完 function BaseUI:onEnterAnimationComplete() end -- 界面退出动画播放完 function BaseUI:onExitAnimationComplete() end -- 界面的prefab加载完成后被调用 function BaseUI:onLoadRootComplete() end -- 当UI对象被创建时调用,注意此时还没加载好ui预制件 function BaseUI:onCreate() end -- 当此界面关闭时调用 function BaseUI:onClose() end -- 当此界面之上的其他界面都关闭,导致此界面处于最顶层时调用,第一次创建此界面的时候不会调用 -- 与onCover配对 function BaseUI:onReshow() end -- 当此界面处于最顶层的时候打开一个新的界面添加到此界面之上时调用 -- 与onReshow配对 function BaseUI:onCover() end -- 当此界面需要刷新时,界面刷新逻辑都在这里面处理 function BaseUI:onRefresh() end -- 当界面隐藏或从隐藏状态恢复显示时 function BaseUI:onUIVisibleChanged() end -- 当界面上的模型激活状态改变时 function BaseUI:onUIModelActiveChanged(active) end -- 当界面层级被重新设置时 function BaseUI:onSetUIOrder() end -- 此界面是否已经关闭了 function BaseUI:isClosed() return self._baseAlreadyClosed end -- 此界面是否被隐藏了,只有当被全屏界面挡住才会隐藏当前界面 function BaseUI:isVisible() return self._baseVisible ~= false end -- 此界面是不是当前最上层的 function BaseUI:isTopUI() return UIManager:getTopUIObj() == self end -- 响应安卓后退事件 function BaseUI:onPressBackspace() end -- 添加粒子之类的特效 function BaseUI:addEffect(effect, parent, order) if self:isClosed() then return end if self._baseEffectList == nil then self._baseEffectList = {} end self._baseEffectList[effect] = 1 if self._baseUIHelper == nil then self._baseUIHelper = self.root:getGameObject():AddComponent(BF_UI_HELPER) end self._baseUIHelper:AddEffect(effect:getComponent(BF_BASE_SORTING_ORDER_HELPER), self._baseUIOrder, order) if parent then effect:setParent(parent, false) end end -- 对已经存在的子节点层级排序 function BaseUI:sortChildOrder(childObj, order) if self._baseUIHelper == nil then self._baseUIHelper = self.root:getGameObject():AddComponent(BF_UI_HELPER) end self._baseUIHelper:AddEffect(childObj:getComponent(BF_BASE_SORTING_ORDER_HELPER), self._baseUIOrder, order) end -- 移除粒子之类的特效 function BaseUI:removeEffect(effect, isNeedCleanUp) if self._baseEffectList then local effectNode = self._baseEffectList[effect] if effectNode then self._baseEffectList[effect] = nil if self._baseUIHelper then self._baseUIHelper:RemoveEffect(effect:getComponent(BF_BASE_SORTING_ORDER_HELPER)) end effect:removeFromParent(isNeedCleanUp) end end end -- 界面的自定义事件添加监听 function BaseUI:addEventListener(key, func, priority) if self:isClosed() then return end local tag = EventManager:addEventListener(key, func, priority) self._baseEventListeners = self._baseEventListeners or {} self._baseEventListeners[key] = tag end -- 界面的自定义事件移除监听 function BaseUI:removeEventListener(key) if self._baseEventListeners and self._baseEventListeners[key] then EventManager:removeEventListener(key, self._baseEventListeners[key]) self._baseEventListeners[key] = nil end end -- 请查看UIManager.UI_TYPE的说明 function BaseUI:getUIType() return UIManager.UI_TYPE.DEFAULT end -- 获取界面索引,其实就是界面lua的路径 function BaseUI:getUIIndex() return self._baseUIIndex end -- 此界面配套的BGM function BaseUI:getBGMId() return nil end function BaseUI:setUIIndex(index) self._baseUIIndex = index end function BaseUI:setUIOrder(siblingIndex, order) self._baseSiblingIndex = siblingIndex self._baseUIOrder = order if self.root then self.root:getTransform():SetSiblingIndex(self._baseSiblingIndex) self._baseRootCanvas.sortingOrder = order if self._baseUIHelper then self._baseUIHelper:SetSortingOrder(order) self:onSetUIOrder() end end end function BaseUI:getUIOrder() return self._baseUIOrder end function BaseUI:getSiblingIndex() return self._baseSiblingIndex end function BaseUI:getIsShowComplete() return self._showComplete end function BaseUI:isFullScreen() return true end function BaseUI:getCurrencyParams() return nil, false end function BaseUI:updateCurrencyBar() UIManager:showCurrencyBar(self) end function BaseUI:swallowTouchBetweenUI() return true end -- 加载需要显示在UI上的模型,统一接口方便管理 function BaseUI:loadUIModelRoot(path, callback) ModelManager:loadModelAsync(path, nil, function(roleShowModel) if self:isClosed() then roleShowModel:destroy() return end self._baseShowModelList = self._baseShowModelList or {} table.insert(self._baseShowModelList, roleShowModel) callback(roleShowModel) if self._baseVisible == false then roleShowModel:setActive(false) self:onUIModelActiveChanged(false) end end) end function BaseUI:bind(data, fieldName, bindFunc, immediately) if self:isClosed() then return end if not self._baseBindData then self._baseBindData = {} end if not self._baseBindData[data] then self._baseBindData[data] = {} end table.insert(self._baseBindData[data], fieldName) data:bind(fieldName, self, bindFunc, immediately) end function BaseUI:unBind(data, fieldName) if self._baseBindData and self._baseBindData[data] then for i, field in ipairs(self._baseBindData[data]) do if field == fieldName then data:unBind(field, self) table.remove(self._baseBindData[data], i) break end end end end function BaseUI:unBindAll() if not self._baseBindData then return end for data, fields in pairs(self._baseBindData) do for _, field in ipairs(fields) do data:unBind(field, self) end end self._baseBindData = nil end function BaseUI:scheduleGlobal(func, inter) if self:isClosed() then return 0 end local sid = SchedulerManager:scheduleGlobal(func, inter) if self._schedulerIds == nil then self._schedulerIds = {} end table.insert(self._schedulerIds, sid) return sid end function BaseUI:performWithDelayGlobal(func, delay) if self:isClosed() then return 0 end local sid = SchedulerManager:performWithDelayGlobal(func, delay) if self._schedulerIds == nil then self._schedulerIds = {} end table.insert(self._schedulerIds, sid) return sid end function BaseUI:pauseScheduleGlobal(sid) SchedulerManager:pause(sid) end function BaseUI:resumeScheduleGlobal(sid) SchedulerManager:resume(sid) end function BaseUI:unscheduleGlobal(sid) if self._schedulerIds == nil then return end for k, v in ipairs(self._schedulerIds) do if v == sid then table.remove(self._schedulerIds, k) break end end SchedulerManager:unscheduleGlobal(sid) end function BaseUI:unloadPreloadList() if self._preloadListFinished then if self._preloadList then for objType, list in pairs(self._preloadList) do for _, path in ipairs(list) do ResourceManager:unload(path) end end self._preloadList = nil end end end function BaseUI:disableUITouch() if not self._baseTouchDisabled and not self:isClosed() then self._baseTouchDisabled = true UIManager:disableTouch() end end function BaseUI:enableUITouch() if self._baseTouchDisabled then self._baseTouchDisabled = false UIManager:enableTouch() end end -- 以下都是private function,不要在外部直接调用 function BaseUI:_onCreate() self:_loadRoot() self:onCreate() end -- 异步加载界面prefab文件,每个界面只能有一个root prefab function BaseUI:_loadRoot() if self.root then return end local totalCount = 0 local function finishCallback() totalCount = totalCount - 1 if totalCount <= 0 then self._preloadListFinished = true if self:isClosed() then self:unloadPreloadList() self:_killAniTween() return end UIManager:getUIPrefab(self:getPrefabPath(), function(root) if self:isClosed() then self:unloadPreloadList() UIManager:putbackUIPrefab(root) return end self._baseUIHelper = root:getComponent(BF_UI_HELPER) self.root = root self:_fitNotchScreen() self:_onLoadRootComplete() -- 个别特殊的界面可能会在init或者refresh的时候关闭自己 if self:isClosed() then self:_onEnterAnimationComplete() return end self:_playEnterAnimation() if self._baseUICovered then self:onCover() end end) end end self._preloadList = self:getPreLoadList() if self._preloadList then for objType, list in pairs(self._preloadList) do totalCount = totalCount + #list end for objType, list in pairs(self._preloadList) do for _, path in ipairs(list) do ResourceManager:loadOriginAssetAsync(path, objType, finishCallback) end end else totalCount = totalCount + 1 finishCallback() end end function BaseUI:_recordParams(params) self._baseUIParams = params end function BaseUI:_getRecordParams() return self._baseUIParams end -- 显示或者隐藏界面,只是给UIManager用来隐藏被遮挡的界面减少渲染压力用的 function BaseUI:_setVisible(visible) if self._baseVisible == visible then return end self._baseVisible = visible if self._baseRootCanvas then local pos = visible and 0 or 10000000000 self.root:setLocalPosition(0, 0, pos) end if self._baseShowModelList then for k, v in ipairs(self._baseShowModelList) do v:setActive(visible) end self:onUIModelActiveChanged(visible) end self:onUIVisibleChanged() end function BaseUI:_getRootCanvas() self._baseRootCanvas = self.root:getComponent(GConst.TYPEOF_UNITY_CLASS.CANVAS) if not self._baseRootCanvas then Logger.logTodo("ui root canvas is not add on prefab:%s", self.root:getAssetPath()) self._baseRootCanvas = self.root:addComponent(GConst.TYPEOF_UNITY_CLASS.CANVAS) end return self._baseRootCanvas end function BaseUI:_onLoadRootComplete() self:_getRootCanvas() self._baseRootCanvas.overrideSorting = true if not self.root:getComponent(GConst.TYPEOF_UNITY_CLASS.GRAPHIC_RAYCASTER) then Logger.logTodo("ui root GraphicRaycaster is not add on prefab:%s", self.root:getAssetPath()) self.root:addComponent(GConst.TYPEOF_UNITY_CLASS.GRAPHIC_RAYCASTER) end if self._baseVisible == false then self.root:setLocalPosition(0, 0, 10000000000) else self.root:setLocalPosition(0, 0, 0) end if self._baseSiblingIndex then self.root:getTransform():SetSiblingIndex(self._baseSiblingIndex) self._baseRootCanvas.sortingOrder = self._baseUIOrder if self._baseUIHelper then self._baseUIHelper:SetSortingOrder(self._baseUIOrder) end end UIManager:showCurrencyBar(self) self:onLoadRootComplete() self:onRefresh() self._baseLoadComplete = true UIManager:onUILoadedComplete(self) if self._onLoadUICompleteCallback then self._onLoadUICompleteCallback() self._onLoadUICompleteCallback = nil end end -- 适配异形屏 function BaseUI:_fitNotchScreen() -- 只有绑定了该组件的UI 才需要适配 if self._baseUIHelper then local notchScreenNodeCount = self._baseUIHelper:GetNotchScreenNodeCount() if notchScreenNodeCount > 0 and not self._baseUIHelper:GetHasInitDefaultNotchScreenHeight() then -- 获取偏移值 local height = SafeAreaManager:getNotchScreenHeight() if height < 1 then -- 没有刘海 return end local index = 0 for i = 1, notchScreenNodeCount do index = i - 1 local haveNode = self._baseUIHelper:GetIsHaveNotchScreenNodeGameObject(index) local adjustHeight = self._baseUIHelper:GetNotchScreenNodeAdjustHeight(index) if haveNode then -- 获取anchor数据 根据情况处理位置 self._baseUIHelper:CacheAnchorMin(index) local anchorMinX = self._baseUIHelper.PositionX local anchorMinY = self._baseUIHelper.PositionY self._baseUIHelper:CacheAnchorMax(index) local anchorMaxX = self._baseUIHelper.PositionX local anchorMaxY = self._baseUIHelper.PositionY if math.abs(anchorMinX - 0.5) < 0.00001 and math.abs(anchorMinY) < 0.00001 and math.abs(anchorMaxX - 0.5) < 0.00001 and math.abs(anchorMaxY - 1) < 0.00001 then -- 上下填充,仅调整高度 self._baseUIHelper:CacheOffsetMax(index) local offsetMaxX = self._baseUIHelper.PositionX local offsetMaxY = self._baseUIHelper.PositionY self._baseUIHelper:SetOffsetMax(index, offsetMaxX, offsetMaxY - height + adjustHeight) elseif math.abs(anchorMinX) < 0.00001 and math.abs(anchorMinY) < 0.00001 and math.abs(anchorMaxX - 1) < 0.00001 and math.abs(anchorMaxY - 1) < 0.00001 then -- 整体填充,仅调整高度 self._baseUIHelper:CacheOffsetMax(index) local offsetMaxX = self._baseUIHelper.PositionX local offsetMaxY = self._baseUIHelper.PositionY self._baseUIHelper:SetOffsetMax(index, offsetMaxX, offsetMaxY - height + adjustHeight) else -- 直接下移 self._baseUIHelper:CacheAnchoredPosition(index) local notchScreenNodeOriginPosX = self._baseUIHelper.PositionX local notchScreenNodeOriginPosY = self._baseUIHelper.PositionY self._baseUIHelper:SetAnchoredPosition(index, notchScreenNodeOriginPosX, notchScreenNodeOriginPosY - height + adjustHeight) end end end self._baseUIHelper:SetInit(true) end end end function BaseUI:_playEnterAnimation() local aniType = UIManager:getAniType(self) if aniType then if aniType == UIManager.ANI_TYPE.POP then self.root:setAnchoredPosition(0, 0) self:_playPop() else UIManager:_updateUISwallowOrder() self.root:setAnchoredPosition(0, 0) self:_onEnterAnimationComplete() end else self.root:setAnchoredPosition(0, 0) self:_onEnterAnimationComplete() end end function BaseUI:_playExitAnimation() self:_onExitAnimationComplete() end function BaseUI:_onEnterAnimationComplete() self._showComplete = true if self:getUIType() == UIManager.UI_TYPE.MAIN then UIManager:closeBehindUI(self) end if self:isFullScreen() then UIManager:hideBehindUI(self) end if self._onEnterAniCompleteCallback then self._onEnterAniCompleteCallback() end self:onEnterAnimationComplete() -- 部分ui会有动画,所以在这里算完成 EventManager:dispatchEvent(EventManager.CUSTOM_EVENT.UI_SHOW_COMPLETE, self:getUIIndex()) end function BaseUI:_onExitAnimationComplete() self:onExitAnimationComplete() if self._onExitAniCompleteCallback then self._onExitAniCompleteCallback() end self:_destroy() UIManager:onUIClosedComplete(self) end function BaseUI:_playPop() self.popSequence = self.root:createBindTweenSequence() local scaleTween1 = self.root:getTransform():DOScale(1.05, 0.15) self.popSequence:Append(scaleTween1) local scaleTween2 = self.root:getTransform():DOScale(1, 0.2) self.popSequence:Append(scaleTween2) self.popSequence:OnComplete(function() UIManager:_updateUISwallowOrder() self:_onEnterAnimationComplete() self.popSequence = nil end) end function BaseUI:_killAniTween() if self.popSequence then self.popSequence:Kill(true) self.popSequence = nil end end function BaseUI:_onCover() self._baseUICovered = true if self.root then self:onCover() end end function BaseUI:_onReshow() if self._baseVisible == false then UIManager:updateBarsState(self) if self:isFullScreen() then UIManager:hideBehindUI(self) else UIManager:showBehindUI(self) end self:_setVisible(true) end self._baseUICovered = false if self.root then self:onReshow() end end function BaseUI:_onClose() EventManager:dispatchEvent(EventManager.CUSTOM_EVENT.UI_CLOSE, self:getUIIndex()) self._baseAlreadyClosed = true self:onClose() self:_baseClear() end function BaseUI:_baseClear() if self._baseEventListeners then for key, tag in pairs(self._baseEventListeners) do EventManager:removeEventListener(key, tag) end self._baseEventListeners = nil end self._baseEffectList = nil if self._schedulerIds then for k, v in ipairs(self._schedulerIds) do SchedulerManager:unscheduleGlobal(v) end self._schedulerIds = nil end self:unBindAll() self:enableUITouch() end function BaseUI:_destroy() if self.root then -- 干掉界面上的模型 if self._baseShowModelList then for i = 1, #self._baseShowModelList do table.remove(self._baseShowModelList):destroy() end end UIManager:putbackUIPrefab(self.root) self:_killAniTween() self.root = nil self._baseRootCanvas = nil end self:unloadPreloadList() end return BaseUI