问题
公司接入weex
大概也有半年多的时间了,sdk
的版本是0.6.4
,自定义的组件一大堆。不过image
这个组件一直没有自定义。
我们的应用有一个“发现”页面,全部是图片,用weex
写的。交互抱怨图片加载后,乱显示的,体验很不好,希望一张一张有顺序地显示。
weex
提供的image
组件,图片下载要自己实现,demo
程序是用SDWebImage
做的,所以这边也是依样画葫芦。SDWebImage
是用NSOperationQueue
实现,默认并发数为3
,是并行加载的,要求图片顺序显示,怎么可能?
所以,这个问题从去年底就提出来了,一直没人去管,并且还记下来一个坑:
目前图片加载顺序, 异步随机
方案1:Native
层自定义组件
-
weex SDK
中<image>
组件对应的Native
类是WXImageComponent
- 关于图片下载的过程,用了一个全局的串行队列,套了好几层主线程做下载任务分发。真正的下载过程,通过协议
WXImageOperationProtocol
让使用者来实现。 - 一般实现都是通过
SDWebImage
这个第三方库来做的,下载图片是并行的,并发数默认是3 - 估计是将
SDWebImage
这种第三方库引入weex
框架,会导致框架本身太庞大,不合适。所以采用了协议加demo
程序这种方式。 - 图片下载过程采用并行方式是合理的,不然,串行下载,性能太差。
- 不过,这里需求是图片顺序显示,那么进行串行下载是最方便的。既然采用
SDWebImage
这个第三方库,只要将它的最大并发数设置为1,编程串行队列下载,就可以了。、 - 默认的
image
组件不变,可以参照写法,稍微改一下,新建一个串行的图片组件,比如seiral-image
就可以了。自定义组件就可以直接用SDWebImage
来实现,不需要WXImageOperationProtocol
来过渡。为了不影响默认的image
组件,下载过程不要用SDWebImage
共享单例,而是新建一个实例变量来做,这样就互不影响。
方案2:通过opacity
属性,串行显示图片
-
native
层组件保持不变,在JavaScript
层来控制 -
if
指令不合适,这个隐藏和native
的隐藏含义不一样,是整个结点都不创建,在这里不适用 - 加载过程保持串行不变。加载完成后,
native
会发送load
事件到JavaScript
层。 - 默认将图片的
opacity
设置为0,就算加载完成了,也看不到图片。在收到load
事件后,将图片的opacity
设置为1,显示图片。这时可以加入一些自定义的逻辑,比如顺序显示等。 - 下面是一个简单的例子:
<template>
<list>
<cell class="cell">
<image class="image" repeat="item in imageList" src="{{item.filePath}}" style="opacity:{{opacitys[$index]}}" onload="imageLoad($index)">
</image>
</cell>
</list>
</template>
<style>
.cell {
height: 212px;
flex-direction: row;
justify-content: space-between;
align-items: stretch;
padding-top: 30px;
padding-left: 24px;
padding-right: 24px;
}
.image {
width: 223px;
}
</style>
<script >
module.exports = {
data: {
imageList: [],
loadFlags:[],
opacitys:[]
},
created: function(){
this.onrefresh();
},
methods: {
onrefresh: function(){
this.displayRefreshing = 'show';
this.refreshing = true;
let stream = require("@weex-module/stream");
var modal = require('@weex-module/modal');
let self = this;
stream.fetch({
url: "/app/image",
indicator: "silent",
}, function(ret) {
self.refreshing = false;
self.displayRefreshing = 'hide';
self.imageList = ret.imageList;
// 只是添加新的元素,旧的元素不更改。函数onrefresh()可能被反复调用,只要第一次将opacity由0变1,不反复更改
var startIndex = self.loadFlags.length;
for (var i = startIndex; i < ret.imageList.length; i++) {
self.loadFlags[i] = false;
self.opacitys.$set(i, 0);
}
// 设定超时保护(30秒),图片全部显示
setTimeout(function() {
self.dispalyAllImages()
}, 30000);
}, function(err){
self.refreshing = false;
self.displayRefreshing = 'hide';
modal.toast({'message': '网络不给力,请稍后再试!', 'duration': 2});
}
);
},
imageLoad: function(index) {
this.loadFlags[index] = true;
this.updateImageOpacity();
},
updateImageOpacity: function() {
// 图片顺序展示
for (var i = 0; i < this.commonLoadFlags.length; i++) {
// 还没加载,就不显示; 要求顺序,后面的就不管了
if (false == this.commonLoadFlags[i]) {
break;
}
// 如果已经显示,就没必要重复设置
if (this.opacitys[i] > 0) {
continue;
}
this.opacitys.$set(i, 1);
}
},
// 超时函数;加载状态不更新;但是显示状态全部设置为显示
dispalyAllImages: function() {
for (var i = 0; i < this.opacitys.length; i++) {
// 如果已经显示,就没必要重复设置
if (this.opacitys[i] > 0) {
continue;
}
this.opacitys.$set(i, 1);
}
}
}
}
</script>
imageList: []
, 存放图片的信息,比如url
就是其中的filePath
属性
-
loadFlags:[]
, 存放加载状态信息,false
- 图片还没加载;true
-图片已经加载 -
opacitys:[]
,存放图片的opacity
值,0-图片隐藏;1-图片显示 - imageLoad(index)为
load
事件响应函数,native
层触发,将图片的序号传给JavaScript
层 -
stream.fetch()
,native
层实现,JavaScript
层调用的网络取数据函数 -
array.$set(index, value)
,是weex
框架提供数组设置函数,将index
的值替换为value
。$set
是函数名。用这个函数,可以结合weex
框架内部的数据绑定机制,可以触发界面元素的重新渲染。 - 在这里,初始化时
self.opacitys.$set(i, 0);
,weex
框架将图片渲染为不可见。当后来self.opacitys.$set(i, 1);``weex
框架将图片渲染为可见。如果直接设置self.opacitys[i] = 0或者1,相应的数值值会改变,但是界面元素不会渲染,也就没有图片从一开始的不可见到可见的过程。 -
setTimeout
函数先套一个匿名函数,然后用另一个变量,比如self
来代替this
,这样调用才有效果。记住这种用法。