Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure my CustomPaint widget painting is stored in the raster cache?

Tags:

flutter

I have an app that displays a black dot at the point where the user touches the screen like this:

enter image description here

The black dot can be moved by the user as he/she drags his finger on the screen.

The background is an expensive paint operation, so I have created two separate widgets in a stack, hoping that the background widget painting will be stored in the Flutter raster cache. But it's not stored - Flutter calls my expensive paint method every time the black dot moves.

What am I doing wrong?

Here's my code:

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  GlobalKey _paintKey = new GlobalKey();
  Offset _offset;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Stack(
        fit: StackFit.expand,
        children: <Widget>[
          new CustomPaint(
            painter: new ExpensivePainter(),
            isComplex: true,
            willChange: false,
          ),
          new Listener(
            onPointerDown: _updateOffset,
            onPointerMove: _updateOffset,
            child: new CustomPaint(
              key: _paintKey,
              painter: new MyCustomPainter(_offset),
              child: new ConstrainedBox(
                constraints: new BoxConstraints.expand(),
              ),
            ),
          )
        ],
      ),
    );
  }

  _updateOffset(PointerEvent event) {
    RenderBox referenceBox = _paintKey.currentContext.findRenderObject();
    Offset offset = referenceBox.globalToLocal(event.position);
    setState(() {
      _offset = offset;
    });
  }
}

class ExpensivePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    print("Doing expensive paint job");
    Random rand = new Random(12345);
    List<Color> colors = [
      Colors.red,
      Colors.blue,
      Colors.yellow,
      Colors.green,
      Colors.white,
    ];
    for (int i = 0; i < 5000; i++) {
      canvas.drawCircle(
          new Offset(
              rand.nextDouble() * size.width, rand.nextDouble() * size.height),
          10 + rand.nextDouble() * 20,
          new Paint()
            ..color = colors[rand.nextInt(colors.length)].withOpacity(0.2));
    }
  }

  @override
  bool shouldRepaint(ExpensivePainter other) => false;
}

class MyCustomPainter extends CustomPainter {
  final Offset _offset;

  MyCustomPainter(this._offset);

  @override
  void paint(Canvas canvas, Size size) {
    if (_offset == null) return;
    canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.black);
  }

  @override
  bool shouldRepaint(MyCustomPainter other) => other._offset != _offset;
}
like image 938
Mark Avatar asked Oct 12 '17 05:10

Mark


People also ask

How to use the custompaint widget in WordPress?

To be able to use the CustomPaint widget, you need to create a class that extends the CustomPainter. The class would have to implement two methods paint () and shouldRepaint (), example: As you can see above, the paint () method will get called whenever the objects needs to repaint and it will have two parameters Canvas and Size.

How to use the custompaint widget without a child?

When using CustomPaint, you have two choices either specify a size property without a child or use the child property giving it a widget. To be able to use the CustomPaint widget, you need to create a class that extends the CustomPainter. The class would have to implement two methods paint () and shouldRepaint (), example:

How does custompaint work?

When asked to paint, CustomPaint first asks its painter to paint on the current canvas, then it paints its child, and then, after painting its child, it asks its foregroundPainter to paint. The coordinate system of the canvas matches the coordinate system of the CustomPaint object.

What is custompaint in flutter?

What is CustomPaint? CustomPaint is a widget from the Flutter SDK, which enables you to use a canvas to draw different shapes. It contains the following properties: painter: The painter that paints before the child. Here you would need to create a class that extends the class CustomPainter


1 Answers

It's a specificity of Flutter. We are not in React, where "Components" are repainted only when their state/props change.

In Flutter, every time a widget has to repaint the whole tree will too.

Usually, this is not a problem and fairly fast. But in some cases (such as yours), you don't want that. And this is where a fairly undocumented but important widget appears! RepaintBoundary

There's an excellent talk about how Flutter's rendering pipeline works, here: https://www.youtube.com/watch?v=UUfXWzp0-DU

But in short, consider RepaintBoundary as what tells Flutter to split the painting operation into different parts.

Anyway, the solution ? Wrap your Expensive widget in a RepaintBoundary. And suddenly you get 60 FPS.

      new RepaintBoundary(
        child: new CustomPaint(
          painter: new ExpensivePainter(),
          isComplex: true,
          willChange: false,
        ),
      ),

enter image description here

like image 75
Rémi Rousselet Avatar answered Oct 01 '22 06:10

Rémi Rousselet