UI框架的功能:
方便快捷的对UI界面进行管理:如快速打开与关闭一个界面,防止相同界面多开等等。
核心代码有2个脚本,一个UIMgr,一个UIBase。
UIMgr的功能是管理UI界面。
UIBase的功能是:所有界面都要继承自UIBase,这样界面就可以直接方便地调用父类的方法,更好地复用代码。管理特殊前缀开头的节点名称,只有使用了特定字符串开头的节点才会被添加到UI管理的数据结构里。
UIMgr里面用到了一个SceneMgr,可以在我的上一篇文章里找到有它的介绍,UIMgr的场景方法和SceneMgr是一样的,同样是一个单例。
import SceneMgr from "./SceneMgr";
/**
* UI界面管理器
*/
export default class UIMgr extends Laya.Script
{
private uiPrefabMap=new Map<string,Laya.Prefab>(); //UI预制体字典:Map<string,Laya.Prefab>
public static Instance: UIMgr;
constructor()
{
super();
UIMgr.Instance=this;
}
/**
* 获取UI预制体的完整路径
* @param uiPrefabName UI预制体名称
*/
public GetPrefabPath(uiPrefabName:string):string
{
return "prefab/"+uiPrefabName+".prefab";
}
//=====================================< UIPanel >类型UI=======================================
/**
* 找到名字对应的UI物体
*/
public FindTargetUI<T extends Laya.UIComponent>(targetName:string,parent:Laya.Node):T
{
if(!parent){
return null;
}
return parent.getChildByName(targetName)as T;
}
/**
* 在场景中找到UI并销毁
*/
public DestroyUI(uiName:string)
{
let sc2d=SceneMgr.Instance.GetCurSc2D();
if(sc2d){
let ui=sc2d.getChildByName(uiName)as Laya.UIComponent;
if(ui){
ui.destroy(true);
}
}
}
/**
* 设置UI visible
*/
public SetUIVisible(uiName:string,visible:boolean)
{
let sc2d=SceneMgr.Instance.GetCurSc2D();
if(sc2d){
let ui=sc2d.getChildByName(uiName)as Laya.UIComponent;
if(ui){
ui.visible=visible;
}
}
}
/**
* 加载并打开UI面板(面板类型UI,不能打开多个)
* @param uiCtlerScript 控制此界面的脚本类
* @param callback 加载完回调(Laya.UIComponent)=>{}
* @param parent 父级节点
* @param isOnlyOne 是否只能存在一个
*/
public OpenUI(uiCtlerScript:any,callback:Function=(param:any)=>{},parent?:Laya.Node,isOnlyOne=true)
{
let uiName=uiCtlerScript.name;
if(!parent){
parent=SceneMgr.Instance.GetCurSc2D();
}
if(!parent){
return;
}
if(parent.getChildByName(uiName)){
if(isOnlyOne==true){
return;
}
}
if(this.uiPrefabMap.has(uiName))
{
let uiPrefab=this.uiPrefabMap.get(uiName) as Laya.Prefab;
this.OpenUICommonOp(uiPrefab,uiCtlerScript,parent,callback);
}
else
{
this.LoadUIPrefab(uiName,(uiPrefab:Laya.Prefab)=>
{
this.OpenUICommonOp(uiPrefab,uiCtlerScript,parent,callback);
});
}
}
/**
* 从字典中移除UI预制体并清理单个资源(最好在切换场景时使用,清理掉下个场景不再使用的UI预制体)
* @param uiCtlerScript 控制此界面的脚本类(ui索引,预制体的名称)
*/
public ClearRes(uiCtlerScript:any)
{
let k=uiCtlerScript.name;
if(!this.uiPrefabMap.has(k))
return;
this.uiPrefabMap.delete(k);
Laya.LoaderManager.prototype.clearRes(this.GetPrefabPath(k));
Laya.Resource.destroyUnusedResources();
}
/**
* 加载UI预制体
* @param uiName 预制体UI名称
* @param callback 加载完回调
*/
private LoadUIPrefab(uiName:string,callback:Function=(uiPrefab:any)=>{})
{
let uiPath=this.GetPrefabPath(uiName);
Laya.loader.load(uiPath,Laya.Handler.create(this,(uiPrefab:Laya.Prefab)=>
{
if(!uiPrefab){
console.error("不存在目标预制体",uiName);
return;
}
this.uiPrefabMap.set(uiName,uiPrefab);
callback(uiPrefab);
}));
}
/**
* 加载面板的共有操作
*/
private OpenUICommonOp(uiPrefab:Laya.Prefab,uiCtlerScript:any,parent:Laya.Node,callback:Function=(ui:any)=>{})
{
let uiName=uiCtlerScript.name;
let ui=uiPrefab.create()as Laya.UIComponent;
parent.addChild(ui);
ui.name=uiName;
this.AddSrcToNode(uiCtlerScript,ui);
callback(ui);
}
private AddSrcToNode(src:any,targetNode:Laya.Node)
{
if(targetNode.getComponent(src))
return;
targetNode.addComponent(src);
}
/**
* 打印字典
*/
public LogUIMapInfo()
{
if(this.uiPrefabMap.size==0||!this.uiPrefabMap==null)
{
console.log("UI预制体资源Map为空");
return;
}
for (let [key, value] of this.uiPrefabMap)
{
console.log("key:",key,"===","value:",value);
}
}
}
UIBase脚本:
当UI界面里的节点使用了prefixArray里的前缀命名时(如:imgTest,txtPlayerName等等),此节点会被添加到allUIDic里,在脚本里可以通过名称获取目标节点。prefixArray里可以自定义添加或删除你的节点前缀。
export default class UIBase extends Laya.Script {
public allUIDic: Map<string, Laya.Node> = new Map<string, Laya.Node>();
private prefixArray: string[] = ["img", "btn", "txt", "list", "box","hsld","ti"]; //需要加入UI字典的UI前缀
constructor() {
super();
}
onAwake() {
this.SetAllUINodesDic();
}
/**
* 销毁当前界面UI
* @param cbOnClose 关闭时的回调
*/
public CloseUI() {
this.owner.destroy(true);
}
private GetNodeByMap<T extends Laya.Node>(nodeName:string,map:Map<string,Laya.Node>):T
{
if(!map.has(nodeName)){
return null;
}
return map.get(nodeName) as T;
}
/**
* 添加点击事件带有声音
* @param btName 按钮名称
* @param callback 回调
* @param needPlayClickSound 是否播放点击音效
* @param clickSoundPath 音效地址
*/
public AddBtnEvent(btName: string, callback: Function,needPlayClickSound=true,clickSoundPath?:string) {
let bt: Laya.Button = this.GetBtn(btName);
if (!bt) return;
bt.on(Laya.Event.CLICK,this,callback)
}
/**
* 通过文本名称获取Tetx组件
* @param txtName 文本组件名称
* @returns Laya.Text
*/
public GetTxt(txtName: string): Laya.Text {
return this.GetNodeByMap<Laya.Text>(txtName, this.allUIDic);
}
/**
* 通过文本名称获取Image组件
* @param imgName Image组件名称
* @returns Laya.Image
*/
public GetImg(imgName: string): Laya.Image {
return this.GetNodeByMap<Laya.Image>(imgName, this.allUIDic);
}
/**
* 通过文本名称获取Image组件
* @param btnName Image组件名称
* @returns Laya.Image
*/
public GetBtn(btnName: string): Laya.Button {
return this.GetNodeByMap<Laya.Button>(btnName, this.allUIDic);
}
/**
* 通过文本名称获取List组件
* @param listName List组件名称
* @returns Laya.List
*/
public GetList(listName: string): Laya.List {
return this.GetNodeByMap<Laya.List>(listName, this.allUIDic);
}
/**
* 通过文本名称获取Box组件
* @param boxName Box组件名称
* @returns Laya.Box
*/
public GetBox(boxName: string): Laya.Box {
return this.GetNodeByMap<Laya.Box>(boxName, this.allUIDic);
}
/**
* 通过文本名称获取HSlider组件
*/
public GetHSlider(name: string): Laya.HSlider {
return this.GetNodeByMap<Laya.HSlider>(name, this.allUIDic);
}
/**
* 通过文本名称获取TextInput组件
*/
public GetTextInput(name: string): Laya.TextInput {
return this.GetNodeByMap<Laya.TextInput>(name, this.allUIDic);
}
/**
* 通过泛型名称获取UI组件
* @param uiName UI组件名称
* @returns Laya.UIComponent
*/
public GetUIByT<T extends Laya.UIComponent>(uiName: string): T {
return this.GetNodeByMap<T>(uiName, this.allUIDic);
}
/**
* 设置UIvisible
*/
public SetVisible<T extends Laya.UIComponent>(uiName: string,visible:boolean)
{
this.GetUIByT<T>(uiName).visible=visible;
}
/**
* 设置文本内容
*/
public SetText(uiName: string,content:string)
{
this.GetTxt(uiName).text=content;
}
/**
* 设置图片的skin
*/
public SetImgSkin(imgName:string,skin:string)
{
this.GetImg(imgName).skin=skin;
}
/**
* 设置按钮的skin
*/
public SetBtnSkin(btnName:string,skin:string)
{
this.GetImg(btnName).skin=skin;
}
//--------------------------------------------添加UI到字典-------------------------------------------------
/**
* 检查目标ui是否需要加入字典
*/
private CheckNeedAddToDic(uiName: string) {
for (let i = 0; i < this.prefixArray.length; i++) {
if (uiName.startsWith(this.prefixArray[i])) {
return true;
}
}
return false;
}
/**
* 将所有的UI节点装入字典(每个ui节点不能重名)
*/
public SetAllUINodesDic() {
this.allUIDic = this.GetAllChildrenMap(this.owner);
}
//获取目标节点的所有子节点,将所有子节点放入数组并返回
private GetChildNodesArray(target: Laya.Node): Laya.Node[] {
let nodeArray: Laya.Node[] = [];
for (let i = 0; i < target.numChildren; i++) {
let node = target.getChildAt(i);
if (node) {
nodeArray.push(node);
}
}
return nodeArray;
}
//递归获取目标节点的所有子孙节点,并将他们全部放入数组并返回
private FindAndGetAllChildren(parentNode: Laya.Node, outNodesArray: Laya.Node[]): Laya.Node[] {
if (parentNode.numChildren > 0) {
let nodeArray = this.GetChildNodesArray(parentNode);
nodeArray.forEach(node => {
if (this.CheckNeedAddToDic(node.name) == true) {
outNodesArray.push(node);
}
if (this.GetChildNodesArray(node).length > 0) {
this.FindAndGetAllChildren(node, outNodesArray);
}
else {
return outNodesArray;
}
});
}
return null;
}
//构建一个数组来存放获取的所有节点并返回此数组
private GetAllChildrenArray(parentNode: Laya.Node): Laya.Node[] {
let allChildrenArray: Laya.Node[] = [];
this.FindAndGetAllChildren(parentNode, allChildrenArray);
return allChildrenArray;
}
//将所有节点封装到字典里,方便获取
private GetAllChildrenMap(parentNode: Laya.Node): Map<string, Laya.Node> {
let allChildrenArray = this.GetAllChildrenArray(parentNode);
let map = new Map();
for (let i = 0; i < allChildrenArray.length; i++) {
if (!map.has(allChildrenArray[i].name)) {
map.set(allChildrenArray[i].name, (allChildrenArray[i]));
}
}
return map;
}
/**
* 打印UI字典
*/
public LogUIMap() {
if (this.allUIDic.size== 0||!this.allUIDic){
console.log("UI预制体资源Map为空");
return;
}
console.log("UI节点个数:", this.allUIDic.size);
for (let [key, value] of this.allUIDic) {
console.log("key:", key, "===", "value:", value);
}
}
}
UI框架使用方法:
在创建UI界面前先要说一下场景的创建,我创建场景使用的是View而不是Scene,因为Scene不好做分辨率适配。
场景创建完成后要把View场景的设计分辨率和四个边距点设置为0。我使用的是1136x640的分辨率。
UI界面制作的流程:比如现在要添加一个名称为TestUIPanel的界面。
我一般创建一个新UI界面是这样做的:
首先创建一个Image名称叫做:TestUIPanel
把Image的skin设置为空,四个边距点设置为0,因为这个Image会作为UI预制体的底板。
如果你的UI需要有黑色半透明背景,可以添加一个Box组件,然后设置它的颜色和透明度,但是4个边距点也要设置为0。
然后在下面添加了一个按钮,点击可以关闭界面。
最后就是保存此UI界面为预制体既可。
Laya默认的UI预制体是在这个目录下的
我加载时使用的也是默认目录。
双击打开UI预制体:
可以点击设置页面调整UI预制体的默认场景大小。
目前UI界面就创建完成了。接下来是编写界面的控制和打开的代码:
创建一个和界面名称一样的脚本,这里必须要保证界面名称和脚本名称一致,因为我是通过把脚本转为字符串去读取的UI预制体。
接着把创建好的脚本继承自UIBase,如果要使用onAwake方法必须调用一下基类的super().onAwake或是执行一下this.SetAllUINodesDic();方法。接下来就是UI的逻辑代码:
打开此界面:
打开界面的方法有四个参数:
@param uiCtlerScript — 控制此界面的脚本类
@param callback — 加载完回调(Laya.UIComponent)=>{}
@param parent — 父级节点,不传则使用当前2d场景作为父节点
@param isOnlyOne — 是否只能存在一个,false时可以打开多个相同界面运行运行:
点击关闭按钮:
以上就是此框架的大概内容,代码也比较简单,可以自己具体看一下。