Flutter - more efficient pan and zoom for CustomPaint
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.
Jellio
Updated on December 14, 2022Comments
-
Jellio over 1 year
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 theCustomPaint
, I basically can't returnfalse
forbool 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.
-
pskink over 4 yearssimply draw only those images that are visible (i am assuming that 100x100 images are not visible at one time)
-
Jellio over 4 yearsThis works most of the time, but the user can zoom out far enough to view the entire grid. I do currently have logic that only draws the images of it's within the visible viewport, but the performance gain isn't good enough.
-
pskink over 4 yearswhat's the point in showing 100x100 grid on one screen? the images would be 10 maybe 20 pixels in size...
-
Jellio over 4 yearsBeing Flutter, I'm not limited to just mobile devices. I plan on using this on the web. 100x100 is not unrealistic.
-
pskink over 4 yearsare you creating brand new
CustomPaint
on every frame? (if yourCustomPainter
does not eat much cpu in its ctor it shouldn't be a big deal but still ...) -
Jellio over 4 yearsYes, I am. I was following the idiomatic Flutter method of building out the tree of Widgets. I can try caching it off.
-
pskink over 4 yearsi would not expect much but try that way: github.com/pskink/matrix_gesture_detector/blob/master/example/… - the whole idea is to use
Listenable repaint
inCustomPainter
ctor - see line #49 -
Jellio over 4 yearsI see. I appreciate the response. After trying that out, I opted to try out the performance profiling tools available in Flutter and saw that the actual computation really just boiled down to the iteration time for hundreds of thousands of cells, even without drawing on the canvas. I think this may boil down to another constraint... maybe the problem isn't the rendering... maybe I should figure out how to store this much data before I optimize how to render it.
-
-
Fourj over 3 yearsThanks for solution. Agreed that transform is better way to improve performance instead of repaint.
-
whidev over 3 yearsCould you please share a snippet of the widget tree you are describing? I am having difficulties implementing your solution.