Jellio
Jellio

Reputation: 1481

Flutter - more efficient pan and zoom for CustomPaint

I'm rendering a collection of grids of tiles, where each tile is pulled from an image. To render this, I'm rendering everything inside my own implementation of CustomPainter (because the grids can get pretty large). To support pan and zoom functionality, I opted to perform the offsetting and scaling as part of canvas painting.

Here is a portion of my custom painting implementation.

  @override
  void paint(Canvas canvas, Size size) {
    // With the new canvas size, we may have new constraints on min/max offset/scale.
    zoom.adjust(
      containerSize: size,
      contentSize: Size(
        (cellWidth * columnCount).toDouble(),
        (cellHeight * rowCount).toDouble(),
      ),
    );

    canvas.save();
    canvas.translate(zoom.offset.dx, zoom.offset.dy);
    canvas.scale(zoom.scale);

    // Now, draw the background image and grids.

While this is functional, performance can start to breakdown after enough cells are rendered (for example, a grid of 100x100 causes some lag on each GestureDetector callback that updates the zoom values). And, because the offsetting and scaling is done in the CustomPaint, I basically can't return false for bool shouldRepaint(MyPainter old) because it needs to repaint to render its new offset and scale.

So, my question is: What is a more performant way of approaching this problem?

I've tried one other approach:

var separateRenderTree = RepaintBoundary(
  child: OverflowBox(
    child: CustomPaint(
      painter: MyPainter(),
    ),
  ),
);
return Transform(
  transform: Matrix4.translationValues(_zoom.offset.dx, _zoom.offset.dy, 0)..scale(_zoom.scale),
  child: separateRenderTree,
);

This also works, but can also get laggy when scaling (translating is buttery smooth).

So, again, what is the right approach to this problem?

Thank you.

Upvotes: 2

Views: 3282

Answers (1)

Jellio
Jellio

Reputation: 1481

Here's where I ended up.

I size my custom painter to be as large as it needs, and then I position it inside a Transform widget (that is top-left aligned with an offset of zero).

On top of this widget I overlay an invisible widget that manages touch inputs. Using a GestureDetector, it will respond to events and notify the Transform widget to update.

With the pan/zoom officially moved out of the painter, I then implemented the "shouldRepaint" function to be more strict.

This has allowed me to render very, very large grids at good-enough speeds.

Upvotes: 5

Related Questions