flutterdartflutter-animationflutter-custompainter

Animating a CustomPainter for Hourglass Effect in Flutter


I'm working on a Flutter application and trying to implement a custom animation that simulates an hourglass effect. I've attached a GIF below to demonstrate the desired animation:

gif

Here's the relevant portion of my code where I define my CustomTimePainter and the main screen setup:

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

class CustomTimePainter extends CustomPainter {
  CustomTimePainter({
    required this.animation,
    required this.backgroundColor,
    required this.color,
  }) : super(repaint: animation);

  final Animation<double> animation;
  final Color backgroundColor, color;

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = const Color.fromARGB(255, 204, 255, 86);
    canvas.drawPaint(paint);

    var paintHourglassAnimation = Paint()
    ..color = Colors.blue;

    canvas.drawPaint(paintHourglassAnimation);

  }

  @override
  bool shouldRepaint(CustomTimePainter old) {
    return animation.value != old.animation.value ||
        color != old.color ||
        backgroundColor != old.backgroundColor;
  }
}

This is my main screen:

 class MainScreen extends StatefulWidget {
    const MainScreen({Key? key}) : super(key: key);
    
    @override
    State<MainScreen> createState() => _MainScreenState();
    }
    
    class _MainScreenState extends State<MainScreen> with TickerProviderStateMixin {
    final CountDownController _countDownController = Get.find();
    final ProjectsController _projectsController = Get.find();
    final SettingsController _settingsController = Get.find();
    
    
    
    @override
    void initState() {
    super.initState();
    _countDownController.createAnimationController(this);
    }
    
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    backgroundColor: Colors.white10,
    body: Center(
    child: AnimatedBuilder(
    animation: _countDownController.controller,
    builder: (context, child) {
      return Stack(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
           Expanded(
              flex: 50,
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Align(
                  alignment: FractionalOffset.center,
                  child: AspectRatio(
                    aspectRatio: 1.0,
                    child: Stack(
                        children: <Widget>[
                          Positioned.fill(
                            child: CustomPaint(
                                painter:
                                    _countDownController.painter),
                          ),
                          Align(
                            alignment: FractionalOffset.center,
                            child: Column(
                              mainAxisAlignment:
                                  MainAxisAlignment.spaceEvenly,
                              crossAxisAlignment:
                                  CrossAxisAlignment.center,
                              children: <Widget>[
                                Obx(
                                  () => Text(
                                    _countDownController
                                        .timerString.value,
                                    style: const TextStyle(
                                        fontSize: 80.0,
                                        color: Colors.white),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
  
                  ),
                ),
              ),
            ),
          ]

I'm using a CustomPainter to try to achieve the animation shown in the GIF. I want to start a timer and have the animation fade in and out like an hourglass timer. However, I'm struggling with implementing the dart:math package to program the animation.

Specifically, I don't know how to animate the var paintHourglassAnimation to achieve the hourglass transition shown in the video.

Can anyone help guide me on how to use the dart:math package for this animation, or suggest a better approach to achieve the desired effect?


Solution

  • You can draw paint like

    import 'dart:ui' as ui;
    
    class CustomTimePainter extends CustomPainter {
      CustomTimePainter({
        required this.animation,
        required this.backgroundColor,
        required this.color,
      }) : super(repaint: animation);
    
      final Animation<double> animation;
      final Color backgroundColor, color;
    
      @override
      void paint(Canvas canvas, Size size) {
        var paint = Paint()..color = backgroundColor;
        canvas.drawPaint(paint);
    
        final top = ui.lerpDouble(0, size.height, animation.value)!;
        Rect rect = Rect.fromLTRB(0, top, size.width, size.height);
        Path path = Path()..addRect(rect);
    
        canvas.drawPath(path, paint..color = color);
      }
    
      @override
      bool shouldRepaint(CustomTimePainter old) {
        return animation.value != old.animation.value ||
            color != old.color ||
            backgroundColor != old.backgroundColor;
      }
    }
    

    And example output

    
    class ANTest extends StatefulWidget {
      const ANTest({super.key});
    
      @override
      State<ANTest> createState() => _ANTestState();
    }
    
    class _ANTestState extends State<ANTest> with SingleTickerProviderStateMixin {
      late AnimationController container =
          AnimationController(vsync: this, duration: Duration(seconds: 3))
            ..repeat();
    
      late Animation<double> animation =
          Tween<double>(begin: 0, end: 1).animate(container);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: LayoutBuilder(
            builder: (_, constraints) => CustomPaint(
              size: constraints.biggest,
              painter: CustomTimePainter(
                color: Colors.amber,
                backgroundColor: Colors.black,
                animation: animation,
              ),
            ),
          ),
        );
      }
    }