fluttercanvasflutter-custompainterflutter-scaffoldflutter-material

Why is CustomPaint affected by whether it is under a Scaffold?


App〈 MaterialApp〈 MyWidget : all is good

Whether I draw the figure on a canvas with a small or a large scale—by replacing v = 100000.0 with v = 1000.0 in the following code, the image doesn't change, as expected.

(If you are not familiar with the idea of using a SizedBox inside a FittedBox, it is explained here.)

scale invariant rendering

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

const v = 10000.0;

class Painter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final mypaint = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 0.012 * v
    ..color = Colors.blue;

    canvas.drawCircle(
      Offset(1.5 * v, 1 * v),
      1 * v, mypaint);
  }

  @override
  bool shouldRepaint(Painter oldDelegate) => false;
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "My App",
      home: FittedBox(
        child: SizedBox(
          width: 3 * v,
          height: 2 * v,
          child: CustomPaint(
            painter: Painter()
            ),
        ),
      ),
    );
  }
}

App〈 Scaffold〈 MaterialApp〈 MyWidget : My chosen size in SizedBox is ignored; why?

But if I put MaterialApp inside a Scaffold, I still get the image on the left for v = 10000.0, but with v = 100.0, I get instead the image on the right.

image changes when scale is changed

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

const v = 10000.0;

class Painter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final mypaint = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 0.012 * v
    ..color = Colors.blue;

    canvas.drawCircle(
      Offset(1.5 * v, 1 * v),
      1 * v, mypaint);
  }

  @override
  bool shouldRepaint(Painter oldDelegate) => false;
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "My App",
      home: Scaffold(
        appBar: AppBar(
          title: const Text('My Title'),
        ),
        body: FittedBox(
          child: SizedBox(
            width: 3 * v,
            height: 2 * v,
            child: CustomPaint(
              painter: Painter()
              ),
          ),
        ),
      ),
    );
  }
}

How do I get a scale invariant CustomPaint with a Scaffold?


Solution

  • As explained in my answer here (saw your comment btw) since FittedBox does not have minimum constrains passed by Scaffold it will shrink as much as possible being the size of the SizedBox the smallest it can get thus changing with v. The solution to this is as simple as passing new constraints telling your FittedBox to expand as much as possible.

    Scaffold(
        appBar: AppBar(
          title: const Text('My Title'),
        ),
        body: ConstrainedBox(
          constraints: const BoxConstraints(
            minWidth: double.infinity,
            minHeight: double.infinity,
          ),
          child: FittedBox(
            child: SizedBox(
              width: 3 * v,
              height: 2 * v,
              child: CustomPaint(painter: Painter()),
            ),
          ),
        ),
      ),