4

I have been trying to develop a drawing app and all the example codes including the one's from The Boring Flutter Development Show don't translate well into real-world usage.

The main problem being that CustomPaint's paint operation is too expensive and re-draws every point, every frame. And as the points increase the flutter app's render time per frame increases significantly

From the time I spent finding a solution to this problem, I found these

  1. RepaintBoundary : Rasterizes layers

  2. Custom Widget using SingleChildRenderObjectWidget and RenderProxyBox : must implement a paint method and no way of passing a controller

I don't think any of the above solutions work well for my needs: smooth canvas drawing operations without re-paint. I even tried simplifying the points and that didn't work either because of the inherent mechanism of CustomPaint

If there was a way to pass a canvas as a widget and attaching a controller it'll be easy to store the captured points and use basic canvas operations like canvas.drawPath() or canvas.drawLine() much efficiently

Any suggestion would be helpful. Thank you!

Dhruva
  • 210
  • 1
  • 14

2 Answers2

4

This is the Controller class

class DrawingController extends ChangeNotifier {

  List<Offset> _points = []
   
  .... // Perform operations on data points
  ....

  void add(Offset point) {
    _points.add(point);
    notifyListeners();
  }

}

This is the CustomPaint class

class Painter extends CustomPainter {
  DrawingController controller;

  Painter(this.controller) : super(repaint: controller); //This is important

  @override
  void paint(Canvas canvas, Size size) {
     //Paint function
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

And in the Gesture detector simply call controller.add(point) to add new points as you grab them

I am not sure this is the most efficient way to paint but it reduced the paint times down to 13ms on a 60hz screen and 9ms on a 120Hz screen. One noticeable drawback is that a single stroke (onTapdownEvent to an onDragEndEvent) renders quite slowly(up to 18ms) when the stroke has many points. This problem disappears as soon as you start a new stroke. I tried asking on several forums and this is the best I could come up with after digging through the flutter source code.

I profiled this functionality on a snapdragon 855 device so the processor is not a bottleneck. If anyone finds a better solution please post it.

Dhruva
  • 210
  • 1
  • 14
0

A helpful tip is to add a Timer.run(setState()). This way it only updates when it can. Instead of loading update1 update2 update3...etc. It loads update1 then if it can it loads update2 but if update1 took to long it goes to update3. In general it just looks smoother but doesn't necessarily speed it up. Sample:

scaleStart(ScaleStartDetails d) => startPoint = d.focalPoint - _offset;

scaleUpdate(ScaleUpdateDetails d) {
  _offset = d.focalPoint - startPoint;
  scale = min(max(1, scale * (d.scale + 13) / 14), 128);
  Timer.run(() => setState(() {}));
}
Everonimo
  • 9
  • 3