This article is produced by The Whisper Team, a startup team that focuses on audio and music technology. We are deeply using Flutter to build cross-platform applications. We hope to explore the possibilities of Flutter on the desktop and mobile.

When developing list pages, we usually use the Lazy Load technique (turned on in MDN) to Load data, but the details of Lazy Loading technique are not covered in this article.

With that Lazy Loading background in mind, what would you do if you had a very large Canvas with tens of thousands of elements all over it and changing all the time?

There is no concept of partial refresh in most Canvas engines, we can only redraw the entire page, which means that if we have 10,000 elements in a Canvas, every frame needs to be rendered

The 10,000 elements, whether it changes or not. Obviously, this leads to very inefficient rendering performance.

In the picture above, we can found that the size of the physical display device is far less than the size of the Canvas itself, on the application at this time there will be a scroll bar to scroll to view the screen for vertical and horizontal direction in addition to the content, but if we don’t do any optimization, so the screen is also part of the calculation and apply colours to a drawing, just did not show, For scenarios like the one above, we do not render anything that is not visible, a concept commonly known as a Viewport.

For Flutter itself, there are widget-ViewPort and many subclasses based on ViewPort to arrange different scenarios. The ViewPort receives a List and automatically controls whether to build and render the widgets based on their layout. In the Canvas scenario, we cannot use these built-in solutions to implement Viewport, because a single Canvas element cannot be used as a Widget, so we need to implement a similar solution ourselves.

The first step is to build a simple box model


class Box {

 double x;

double y;

double width;

double height;

void render();

}

Copy the code

The second step is to build a simple Viewport, horizontalScrollController for horizontal scrolling, verticalScrollController for vertical scroll.


class Viewport extends Box {

List<Box> children;

ScrollController horizontalScrollController;

ScrollController verticalScrollController;

}

Copy the code

Add events for scrolling


initEvent(){

horizontalScrollController.addListener(render);

verticalScrollController.addListener(render);

}

render() {

   var offsetX = horizontalScrollController.offset;

   var offsetY = verticalScrollController.offset;

    // Here we simply use the container's Viewport area to create a Viewport

   var  startX = offsetX - width;

   var endX = offsetX + width;

  var startY = offsetY - height;

  var endY = offsetY + height;

// Filter invisible elements directly
  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();
  });

}

Copy the code

Since then, we have created a minimal Canvas Lazy Render model that listens for scroll events, calculates the elements of the visible region based on the current scroll offset, and then filters the Render. In this way, the application in the super-large Canvas is a technical solution with great performance improvement.

Reference data

  • Salted fish – PowerScrollView