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

282 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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()),
        ),
      ),
    ),
  ),
3
On

The custom paint is independant of the size. It takes the size sent from it's caller. If you set the sizedbox's size to 200x200 it will paint in that space. Now 10k or 1k will give same result since there is no space left on the device to paint something since width of device is less than 1k. But 10, 100 and 1k will show different sized circles. The only factor that defines the scale here would be the size of the SizedBox

The draw circle can also be written as

canvas.drawCircle(Offset(size.height / 2, size.width / 2), size.width/2);

Now if you pass a sized box of 100x100 it will draw a circle of diameter 100