[LayaAir 1.0]Laya中Button等组件lose skin的问题

在UI界面给组件设置皮肤时遇到的问题,Button组件丢失了皮肤”lose skin wxlocal/enemy.png”无法正常显示,但是旁边的Image组件却能正常的展示。
1.png

设计模式中的两架飞机,上面那架是Image组件,下面那架为Button组件
2.png

实际画面展示的时候却只有上面那架飞机能显示出来
资源也都进行了预加载。
//程序入口
Laya.init(852, 480, Laya.WebGL); 
let resArr = [
             {url:"wxlocal/enemy.png",type:Laya.Loader.IMAGE},
             {url:"wxlocal/bg2.jpg",type:Laya.Loader.IMAGE}
]; 
Laya.loader.load(resArr, Laya.Handler.create(null, onLoaded)); 
function onLoaded(): void {
    Laya.URL.basePath ="https://xxx.com/";
    //实例UI界面
    var Main: MenuPageUI = new MenuPageUI();
    Laya.stage.addChild(Main);
}
在我找原因的时候,我发现主要是和设置了基础路径有关,也发现了3种可以正常展示飞机的做法。
做法A:直接将Laya.URL.basePath ="https://xxx.com/";设置基础路径这一行删除。
做法B:将Laya.URL.basePath ="https://xxx.com/";设置基础路径这一行提前到 Laya.loader.load(resArr, Laya.Handler.create(null, onLoaded)); 之前。
做法C:在资源渲染完之后再设置基础路径。
 
根据官方的文档(官方本地资源和动态资源加载的文档),wxlocal(默认本地目录)目录被视为本地目录,即便使用了URL.basePath,对于包含在nativefiles白名单内的目录名或文件,都不会从网络动态加载,只会从本地加载。但相同的皮肤设置下,最终Image组件可以正常展示,而Button组件则会lose skin。
 
那么问题肯定是在两个组件之间存在的差异。
 
从官方文档中可以看到:Image 类是用于表示位图图像或绘制图形的显示对象。 Image和Clip组件是唯一支持异步加载的两个组件,比如img.skin = "abc/xxx.png",其他UI组件均不支持异步加载。
 
知道了差异再去看看在laya.ui.js中,具体的处理方式是如何的:
在laya.ui.js中找到Button设置皮肤的地方,发现它会去调用changeClips

__getset(0,__proto,'skin',function(){
        return this._skin;
        },function(value){
        if (this._skin !=value){
            this._skin=value;
            this.callLater(this.changeClips);
            this._setStateChanged();
        }
    });
 
在changeClips我们可以发现,img如果没有取到就会打印lose skin,并且直接返回。
__proto.changeClips=function(){
        var img=Loader.getRes(this._skin);
        if (!img){
            console.log("lose skin",this._skin);
            return;
        };
        var width=img.sourceWidth;
        var height=img.sourceHeight / this._stateNum;
        img.$_GID || (img.$_GID=Utils.getGID());
        var key=img.$_GID+"-"+this._stateNum;
        var clips=WeakObject.I.get(key);
        if (!Utils.isOkTextureList(clips)){
            clips=null;
        }
        if (clips)this._sources=clips;
        else {
            this._sources=;
            if (this._stateNum===1){
                this._sources.push(img);
                }else {
                for (var i=0;i < this._stateNum;i++){
                    this._sources.push(Texture.createFromTexture(img,0,height *i,width,height));
                }
            }
            WeakObject.I.set(key,this._sources);
        }
        if (this._autoSize){
            this._bitmap.width=this._width || width;
            this._bitmap.height=this._height || height;
            if (this._text){
                this._text.width=this._bitmap.width;
                this._text.height=this._bitmap.height;
            }
            }else {
            this._text && (this._text.x=width);
        }
    }
 
再去看看laya.core.js中的getRes是如何运作的。
/**
    *获取指定资源地址的资源。
    *@param url 资源地址。
    *@return 返回资源。
    */
    __proto.getRes=function(url){
        return Loader.getRes(url);
    }
 
Loader.getRes:
Loader.getRes=function(url){
        return Loader.loadedMap[URL.formatURL(url)];
    }
 
URL.formatURL:
URL.formatURL=function(url,base){
        if (!url)return "null path";
        if (url.indexOf(":")> 0)return url;
        if (URL.customFormat !=null)url=URL.customFormat(url,base);
        var char1=url.charAt(0);
        if (char1==="."){
            return URL.formatRelativePath((base || URL.basePath)+url);
            }else if (char1==='~'){
            return URL.rootPath+url.substring(1);
            }else if (char1==="d"){
            if (url.indexOf("data:image")===0)return url;
            }else if (char1==="/"){
            return url;
        }
        console.log("最终返回的地址",(base || URL.basePath)+url);
        return (base || URL.basePath)+url;
    }
 
打印出最终返回的地址可以发现这是带着基础路径的地址,而我们缓存的地址为wxlocal/enemy.png,这并不是我们想要的结果。
9.png

因为这个地址中没有资源所以getRes中的Loader.loadedMap[URL.formatRUL(url)]得到的肯定也是null,皮肤资源也就设置失败了。
 
在来看看image的处理方式有什么不同。
__getset(0,__proto,'skin',function(){
        return this._skin;
        },function(value){
        if (this._skin !=value){
            this._skin=value;
            if (value){
                var source=Loader.getRes(value);
                if (source){
                    this.source=source;
                    this.onCompResize();
                }else Laya.loader.load(this._skin,Handler.create(this,this.setSource,[this._skin]),null,/*laya.net.Loader.IMAGE*/"image",1,true,this._group);
                }else {
                this.source=null;
            }
        }
    });
 
和Button一样,getRes返回的也是null,但是Image多做了一件事Laya.loader.load

__proto.load=function(url,complete,progress,type,priority,cache,group,ignoreCache){
        var _$this=this;
        (priority===void 0)&& (priority=1);
        (cache===void 0)&& (cache=true);
        (ignoreCache===void 0)&& (ignoreCache=false);
        if ((url instanceof Array))return this._loadAssets(url,complete,progress,type,priority,cache,group);
        var content=Loader.getRes(url);
        if (content !=null){
            Laya.timer.frameOnce(1,null,function(){
                progress && progress.runWith(1);
                complete && complete.runWith(content);
                _$this._loaderCount || _$this.event(/*laya.events.Event.COMPLETE*/"complete");
            });
            }else {
            var info=LoaderManager._resMap;
            if (!info){
                info=this._infoPool.length ? this._infoPool.pop():new ResInfo();
                info.url=url;
                info.type=type;
                info.cache=cache;
                info.group=group;
                info.ignoreCache=ignoreCache;
                complete && info.on(/*laya.events.Event.COMPLETE*/"complete",complete.caller,complete.method,complete.args);
                progress && info.on(/*laya.events.Event.PROGRESS*/"progress",progress.caller,progress.method,progress.args);
                LoaderManager._resMap=info;
                priority=priority < this._maxPriority ? priority :this._maxPriority-1;
                this._resInfos[priority].push(info);
                this._next();
                }else {
                complete && info._createListener(/*laya.events.Event.COMPLETE*/"complete",complete.caller,complete.method,complete.args,false,false);
                progress && info._createListener(/*laya.events.Event.PROGRESS*/"progress",progress.caller,progress.method,progress.args,false,false);
            }
        }
        return this;
    }
 
可以看到content和info都是取不到的,但是它会去生成一个info并存到LoaderManager._resMap中。
14.png

然后Laya.loader.load加载结束回调setSource,在回调中成功的设置皮肤。
 
总结一下,产生Image和Button这两个组件差异的主要原因有两个:
第一是在项目中加上基础路径后,formatURL在判断的时候对本地资源地址的处理加上了基础路径。
第二是Button不能像Image那样在资源未加载时,自动加载并缓存。
 
这时候再去看前面所说的三个做法:
做法A中,因为没有加基础路径,所以本地缓存的资源路径为:wxlocal/enemy.png,formatURL中的资源路径为:wxlocal/enemy.png
做法B中,因为在设置本地缓存路径前就加了基础路径,所以本地缓存的资源路径为:基础路径+wxlocal/enemy.png,formatURL中的资源路径为:基础路径+wxlocal/enemy.png
做法C中,因为在图片渲染之后在设置的基础路径,所以本地缓存的资源路径为:wxlocal/enemy.png,formatURL中的资源路径为:wxlocal/enemy.png
 
 

本质的解决方法:
其实主要的问题是第一个问题,第一个问题解决了,第二个问题也不会出现(第二点算是拓展Button组件功能吧)。

第一个问题我们可以在formatURL中加个判断,无论怎样只要是本地白名单中的资源,都不加基础路径直接返回。
这里我们在laya.wxmini.js中更改(不同的平台可以在不同的适配库中修改)。
在MiniAdpter.init中加入这两行:
MiniAdpter.init=function(isPosMsg,isSon){
        MiniAdpter.EnvConfig.formatURL=URL["formatURL"];
        URL["formatURL"]=MiniAdpter.formatURL;
}
 
并且自己重写formatURL
MiniAdpter.formatURL=function(url,base){
        if (MiniFileMgr.isLocalNativeFile(url)){
            if (URL.customFormat !=null) {
                return URL.customFormat(url,base);
            }
            return url;
        };
        
        return MiniAdpter.EnvConfig.formatURL(url, base);
    } 
第二个问题我们可以在laya.ui.js中Button的处理皮肤地址时参考Image的处理方式来实现。
__getset(0,__proto,'skin',function(){
        return this._skin;
        },function(value){
        if (this._skin !=value){
            this._skin=value;
            if(!Loader.getRes(this._skin)){
                Laya.loader.load(this._skin,Handler.create(this,this.setSource,[this._skin]),null,/*laya.net.Loader.IMAGE*/"image");
            }else{
                this.setSource();
            }
        }
    }); 
    /**
    *@private
    *设置皮肤资源。
    */
    __proto.setSource=function(url,img){
            this.callLater(this.changeClips);
            this._setStateChanged();
    }
 
附件中有一个简单的demo,Laya版本为1.7.22。
已邀请:

要回复问题请先

商务合作
商务合作