0

In Flutter, is a Custom Painter and all its class variables re-constructed from scratch every time the paint method is called? -- when setState is called in the parent?? I didn't expect that, but it seems to be the case:

I ask because I have a Custom Painter that contains an object inside the paint method (a sequence of points) that is the basis of all the subsequent painting effects. But... this object takes math in the first place just to be created... and it requires the canvas dimensions as part of that creation... which is why its inside the paint method.

So... I thought... instead of calculating the same exact skeleton shape every time paint is called, I'll make it a nullable class variable and initialize it once... then just check if its null every time in paint instead of recreating it every time.

I thought this was best practice to "move calculations out of the paint method when possible."

BUT... the unexpected result is that when I check, Flutter always says my object is null (it's recreated every time anyway).

Custom Painter:

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

class StackPainter02 extends CustomPainter {
  final List<double> brightnessValues;

  Paint backgroundPaint = Paint();
  Paint segmentPaint = Paint();

  List<Offset>? myShape;

  StackPainter02({
    required this.brightnessValues,
  }) {

    backgroundPaint.color = Colors.black;
    backgroundPaint.style = PaintingStyle.fill;
    segmentPaint.style = PaintingStyle.stroke;
  }

  @override
  void paint(Canvas canvas, Size size) {
    final W = size.width;
    final H = size.height;

    segmentPaint.strokeWidth = W / 100;

    canvas.drawPaint(backgroundPaint);

    // unfortunately, we must initialize this here because we need the view dimensions
    if (myShape == null) {
      myShape = _myShapePoints(brightnessValues.length, 0.8, W, H, Offset(W/2,H/2));
    }

    for (int i = 0; i<myShape!.length; i++) {

      // bug fix... problem: using "i+1" results in index out-of-range on wrap-around
      int modifiedIndexPlusOne = i;
      if (modifiedIndexPlusOne == myShape!.length-1) {
        modifiedIndexPlusOne = 0;
      } else {
        modifiedIndexPlusOne++;
      }
      // draw from point i to point i+1
      Offset segmentStart = myShape![i];
      Offset segmentEnd = myShape![modifiedIndexPlusOne];

      double b = brightnessValues[i];
      if (b < 0) {
        b = 0; // !!!- temp debug... problem: brightness array algorithm is not perfect
      }
      int segmentAlpha = (255*b).toInt();
      segmentPaint.color = Color.fromARGB(segmentAlpha, 255, 255, 200);

      canvas.drawLine(segmentStart, segmentEnd, segmentPaint);

    }
  }

  @override
  bool shouldRepaint(covariant StackPainter02 oldDelegate) {
    //return (oldDelegate.brightnessValues[32] != brightnessValues[32]); // nevermind
    return true;
  }
  
}

Build Method in Parent:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: CustomPaint(
          painter: //MyCustomPainter(val1: val1, val2: val2),
              StackPainter02(
            brightnessValues: brightnessValues,
          ),
          size: Size.infinite,
        ),
      ),
    );
  }

PS - A ticker is used in parent and the "brightnessValues" are recalculated on every Ticker tick -> setState

Nerdy Bunz
  • 6,040
  • 10
  • 41
  • 100
  • 1
    Exactly what are you observing? Where are you storing and constructing your `CustomPainter`? Show some code (ideally a minimal but complete example that reproduces what you're observing). – jamesdlin Apr 22 '22 at 05:46
  • 1
    this is because you are using `child: CustomPaint(painter: YourCustomPainter())` right? if so, you are calling `YourCustomPainter` ctor every time the ui tree is built (that is every time `setState` is called) – pskink Apr 22 '22 at 05:46
  • You asked for it. hehe. – Nerdy Bunz Apr 22 '22 at 05:58
  • 1
    As @pskink said, the parent's `build` method constructs a new, anonymous `StackPainter02` object every time it's called, so what do you expect? If you don't want it to be recreated every time, you need to actually store that state somewhere and reuse it. – jamesdlin Apr 22 '22 at 06:09
  • Hmmm... yes I suppose on the one hand I did expect it to be rebuilt... but on the other hand... I don't understand how refactoring *anything* out of the paint method is supposed to help performance if the entire class is going to be destroyed anyway. I suppose I just need to figure out how to get the dimensions of the Custom Painter somehow externally. – Nerdy Bunz Apr 22 '22 at 06:14

0 Answers0