Search code examples
flutterflutter-layoutflutter-canvas

Custom Painter class not visible in Stack flutter


For some reason, the Stack widget does not show the Container with the CustomPaint.

However if removed from Stack, it works fine. What am I missing here?

class _DemoNavBar extends State<DemoNavBar> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        home:
        Stack(
          children: <Widget>[
            Container(child: CustomPaint(painter: CurvePainter()))
      ],
    )
    );
  }
}

class CurvePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint();
    paint.color = Colors.green[800];
    paint.style = PaintingStyle.fill;

    var path = Path();

    path.moveTo(0, size.height - 100); 

    path.lineTo(size.width * 0.5, size.height - 100); 
    path.quadraticBezierTo(size.width * 0.7, size.height, size.width * 0.9,
        size.height - 100); 
    path.lineTo(size.width, size.height - 100); 
    path.lineTo(size.width, size.height); 
    path.lineTo(0, size.height);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

Thanks!


Solution

  • Checking the source of CustomPaint it says

      /// The size that this [CustomPaint] should aim for, given the layout
      /// constraints, if there is no child.
      ///
      /// Defaults to [Size.zero].
      ///
      /// If there's a child, this is ignored, and the size of the child is used
      /// instead.
    

    So, give it a size. Other solutions include 1) providing width and height to the parent Container of CustomPaint and 2) provide a child for the CustomPaint which will ignore the size provided in the solution below.


    I checked this code to work fine. size: MediaQuery.of(context).size uses the complete screen size.

    void main() {
      runApp(SO());
    }
    
    class SO extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: DemoNavBar(),
        );
      }
    }
    
    class DemoNavBar extends StatefulWidget {
      @override
      _DemoNavBar createState() => _DemoNavBar();
    }
    
    class _DemoNavBar extends State<DemoNavBar> {
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            CustomPaint(
              size: MediaQuery.of(context).size,
              painter: CurvePainter(),
            )
          ],
        );
      }
    }
    
    class CurvePainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        var paint = Paint();
        paint.color = Colors.green[800];
        paint.style = PaintingStyle.fill;
    
        var path = Path();
    
        path.moveTo(0, size.height - 100);
    
        path.lineTo(size.width * 0.5, size.height - 100);
        path.quadraticBezierTo(size.width * 0.7, size.height, size.width * 0.9, size.height - 100);
        path.lineTo(size.width, size.height - 100);
        path.lineTo(size.width, size.height);
        path.lineTo(0, size.height);
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }
    

    Now, the reason is since Container has no parent or child to provide the size it takes complete screen size and works fine without Stack. When a stack is used size goes to zero which is given to the custom painter.

    Equivalent code can be written as

    Stack(
      children: <Widget>[
        Container(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          child: CustomPaint(
            painter: CurvePainter(),
          ),
        )
      ],
    );
    

    the end result is

    screenshot