本文由小声团队出品,小声团队是一个专注于音频&音乐技术的初创团队,深度使用Flutter构建跨平台应用,希望与大家一起共同探索Flutter在桌面端&移动端的可能性。
当我们在进行列表页开发的时候,通常会使用Lazy Load 技术 (在MDN中打开)来加载数据,关于Lazy Loading技术的细节本文不再赘述。
在我们有了Lazy Loading的背景知识之后,我们一起来思考如果你现在面对一个超大型的Canvas(画布),上面可能布满了上万个元素,并且随时在变动,那么你会怎么处理?
在大多数Canvas引擎中都没有局部刷新的概念,我们只能重绘整个页面,这也就意味着,如果我们一个Canvas里面有1万个元素,那么每一帧都需要渲染
这一万个元素,无论它是否变动。很显然,这形成了非常低效的渲染性能。
在上面的图片中,我们可以发现物理显示设备的尺寸是远远低于Canvas本身的大小的,在应用上这个时候会有滚动条来进行垂直与横向方向滚动来查看屏幕之外的内容,但是如果我们不做任何的优化,那么屏幕之外的内容也被计算与渲染了,仅仅只是没有显示而已,对于上面的这样的场景,我们的做法是对于可见范围之外的内容不进行渲染,这个概念通常叫做Viewport(视区)。
对于Flutter本身而言,已经提供了Widget - ViewPort 以及众多基于ViewPort的子类来进行不同场景的编排,Viewport接收一个List<Widget>,自动根据Widget的布局来控制是否build与渲染。在Canvas场景中,我们则没有办法使用这些内置的方案来实现Viewport,因为单个Canvas元素没法作为一个Widget,因此我们需要自己来实现一套类似的方案。
第一步,我们先构建一个简单的盒模型
class Box {
double x;
double y;
double width;
double height;
void render();
}
第二步,构建一个简单的Viewport类,horizontalScrollController 为横向滚动,verticalScrollController为纵向滚动。
class Viewport extends Box {
List<Box> children;
ScrollController horizontalScrollController;
ScrollController verticalScrollController;
}
对滚动添加事件
initEvent(){
horizontalScrollController.addListener(render);
verticalScrollController.addListener(render);
}
render() {
var offsetX = horizontalScrollController.offset;
var offsetY = verticalScrollController.offset;
//这里我们简单的用容器的可视区域来建立Viewport
var startX = offsetX - width;
var endX = offsetX + width;
var startY = offsetY - height;
var endY = offsetY + height;
// 将不可见元素直接过滤
children
.where((e) => e.x + e.width >= startX && e.x + e.width <= endX && e.y + e.height >= startY && e.y + e.height<= endY)
.forEach((e){
e.render();
});
}
自此,我们就已经完成了一个最小的Canvas Lazy Render模型,其原理就是监听滚动事件,按照当前的滚动偏移量计算可见区域的元素,然后进行过滤渲染。这样,应用在超大规模的Canvas中是有极大性能提升的技术方案。