How to properly fit a CustomPainter Animation in the screen?

Viewed 1241

Actually I tried to implement a Timer. The custom painter turned out pretty good. But when I tried to put the custom painter in a row or Column it overflows. The custom paint is only displayed when I explicitly define the height and width of the container. Now I need to add a Timer String in a row with the custom paint but it doesn't work. The height and width of the container take the full size of the screen not allowing any other widget to display.

@override
  Widget build(BuildContext context) {
    double height = MediaQuery.of(context).size.height;
    double width = MediaQuery.of(context).size.width;

    return Scaffold(
      backgroundColor: Styles.primaryDarkBlue,
      body:  SafeArea(
              child: Container(
          height: height,
          width: width,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: CustomPaint(
            painter: TimerBar(
                  animation: animation,
                )),
          ),
        ),
      ));
  }

Here is the full code.

import 'package:flutter/material.dart';


import 'package:focus7/styles.dart';

class Timer extends StatefulWidget {
  @override
  _TimerState createState() => _TimerState();
}

class _TimerState extends State<Timer> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  String get timerString {
    Duration duration = controller.duration * controller.value;
    return "${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, "0")}";
  }

  @override
  void initState() {

    super.initState();
    controller = AnimationController(vsync: this, duration: Duration(seconds: 7));
    animation = Tween<double>(begin: 0.1, end: 1).animate(controller);
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    double height = MediaQuery.of(context).size.height;
    double width = MediaQuery.of(context).size.width;
    print(height);

    return Scaffold(
      backgroundColor: Styles.primaryDarkBlue,
      body:  SafeArea(
              child: Container(
          height: height,
          width: width,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: CustomPaint(
            painter: TimerBar(
                  animation: animation,
                )),
          ),
        ),
      ));


  }
}

class TimerBar extends CustomPainter {
  final Gradient gradient = Styles.primaryGradient;
  final Color timerBarColor = Styles.primaryBlue;
  final Animation animation;

  TimerBar({this.animation}) : super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = new Paint()
      ..color = timerBarColor
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5;

    Rect rect = new Rect.fromLTWH(0, 0, size.width, size.height / 25);

    RRect rrectBorder = new RRect.fromRectAndRadius(rect, Radius.circular(50));
    canvas.drawRRect(rrectBorder, paint);

    paint.style = PaintingStyle.fill;
    paint.strokeWidth = 0;
    paint.shader = gradient.createShader(rect);

    Rect rectAnim = new Rect.fromLTWH(0, 0, size.width * animation.value, size.height / 25);
    RRect rrect = new RRect.fromRectAndRadius(rectAnim, Radius.circular(50));

    canvas.drawRRect(rrect, paint);

  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return false;
  }
}

here is the output:

enter image description here

when I tried to nest in a row:

return Scaffold(
        backgroundColor: Styles.primaryDarkBlue,
        body: SafeArea(
          child: Row(
            children: <Widget>[
              Container(
                height: height,
                width: width,
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CustomPaint(
                      painter: TimerBar(
                    animation: animation,
                  )),
                ),
              ),
              AnimatedBuilder(animation: controller, builder: (context, child) {
                return Text(timerString,style: TextStyle(fontSize:24 ),);
              })
            ],
          ),
        ));

enter image description here

2 Answers

This can be done with an Expanded widget, and by passing the width and height explicitly to the Timer() class.

I created this dartpad with the code so that you could run it (I had to change some of the styling to make it work): http://dartpad.dev/eb69452a5c577d1a8286c6dd1a56e331

First, the Expanded widget I used in the Scaffold:

return Scaffold(
  backgroundColor: Colors.yellow,
  body: SafeArea(
    child: Row(
      children: <Widget>[
        Expanded(  // <----------------------------------
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: CustomPaint(
              painter: TimerBar(
                width: width,
                height: height,
                animation: animation,
              ),
            ),
          ),
        ),
        AnimatedBuilder(
          animation: controller,
          builder: (context, child) {
            return Text(
              timerString,
              style: TextStyle(fontSize: 24),
            );
          },
        )
      ],
    ),
  ),
);

This worked, but the height and width of the timer bar wasn't sized according to the mediaquery, so I made explicit variables to pass into the class:

class TimerBar extends CustomPainter {
  final Gradient gradient = LinearGradient(
      begin: Alignment.topRight,
      end: Alignment.bottomLeft,
      colors: [Colors.blue, Colors.red]);

  final Color timerBarColor = Colors.blue;
  final Animation animation;
  final width;     // <----------------------------------
  final height;    // <----------------------------------

  TimerBar({this.animation, this.width, this.height}) // <----------------
      : super(repaint: animation);
...

and then I used those widths for your two rectangle objects:

Rect rect = new Rect.fromLTWH(0, 0, width, height / 25); // <-------------

RRect rrectBorder = new RRect.fromRectAndRadius(rect, Radius.circular(50));
canvas.drawRRect(rrectBorder, paint);

paint.style = PaintingStyle.fill;
paint.strokeWidth = 0;
paint.shader = gradient.createShader(rect);

Rect rectAnim =
    new Rect.fromLTWH(0, 0, width * animation.value, height / 25);  // <------------
RRect rrect = new RRect.fromRectAndRadius(rectAnim, Radius.circular(50));

I have found a solution after many trial and errors....The below code looks fine.

return Scaffold(
        backgroundColor: Styles.primaryDarkBlue,
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(10),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Expanded(
                  flex: 5,
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: AnimatedBuilder(
                        animation: controller,
                        builder: (context, child) {
                          return CustomPaint(painter: TimerBar(animation: animation, height: height));
                        }),
                  ),
                ),
                Expanded(
                  flex: 1,
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: AnimatedBuilder(
                        animation: controller,
                        builder: (context, child) {
                          return Text(
                            timerString,
                            style: TextStyle(fontSize: 24, color: Styles.primaryWhite),
                          );
                        }),
                  ),
                )
              ],
            ),
          ),
        ));

output:

enter image description here

Related