25

I'm trying to animate a custom dialog box in dart so that when it pops up it create some animations. There is a library in Android that is having animated dialog boxes, is there any similar library in Flutter Sweet Alert Dialog

how can we achieve the same functionality in flutter?

Ammy Kang
  • 11,283
  • 21
  • 46
  • 68
  • Flutter is yet not in production, wait for some time, and you would see some sort of libraries like that. Till then you can run the above library in flutter too but that will only work for Android. – CopsOnRoad Sep 19 '18 at 15:56
  • @CopsOnRoad there is no reason to wait! Flutter already supports all the features to build a custom style popup like the given. – NiklasPor Nov 07 '18 at 14:05
  • @Niklas I wrote that comment almost one and half months ago and since then Flutter has added so many stuff. Anyways, please check your `initState()` method, it contains few lines which are irrelevant. – CopsOnRoad Nov 08 '18 at 07:50

6 Answers6

36

To create dialog boxes you can use the Overlay or Dialog classes. If you want to add animations like in the given framework you can use the AnimationController like in the following example. The CurvedAnimation class is used to create the bouncing effect on the animation.

Update: In general it is better to show dialogs with the showDialog function, because the closing and gesture are handled by Flutter. I have updated the example, it is now running with showDialog and you are able to close the dialog by tapping on the background.

Bouncing Dialog Animation

You can copy & paste the following code into a new project and adjust it. It is runnable on it's own.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', theme: ThemeData(), home: Page());
  }
}

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton.icon(
            onPressed: () {
              showDialog(
                context: context,
                builder: (_) => FunkyOverlay(),
              );
            },
            icon: Icon(Icons.message),
            label: Text("PopUp!")),
      ),
    );
  }
}

class FunkyOverlay extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => FunkyOverlayState();
}

class FunkyOverlayState extends State<FunkyOverlay>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> scaleAnimation;

  @override
  void initState() {
    super.initState();

    controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 450));
    scaleAnimation =
        CurvedAnimation(parent: controller, curve: Curves.elasticInOut);

    controller.addListener(() {
      setState(() {});
    });

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Material(
        color: Colors.transparent,
        child: ScaleTransition(
          scale: scaleAnimation,
          child: Container(
            decoration: ShapeDecoration(
                color: Colors.white,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(15.0))),
            child: Padding(
              padding: const EdgeInsets.all(50.0),
              child: Text("Well hello there!"),
            ),
          ),
        ),
      ),
    );
  }
}
NiklasPor
  • 9,116
  • 1
  • 45
  • 47
  • 1
    you are not allowed to use `setState()` in `initState()` – CopsOnRoad Nov 08 '18 at 07:47
  • 1
    Hey @CopsOnRoad, `setState()` is just registered as the handler for the animation. It is not called while running `initState()`. Have a look at the [Animation Tutorial](https://flutter.io/docs/development/ui/animations/tutorial), it also registers `setState()` in `initState()`. – NiklasPor Nov 08 '18 at 07:52
  • 1
    Sorry I didn't pay attention that it was registered for the animation listener. Still one more mistake you have in your code, `super.initState()` has to be the first line in `initState()` method. – CopsOnRoad Nov 08 '18 at 08:19
  • @CopsOnRoad no problem :) Totally forget to move super call up, I'll edit the snippet. Thanks! – NiklasPor Nov 08 '18 at 08:27
  • Thanks but I have one more question: how to close the overlay? – RockerFlower Jan 21 '19 at 16:02
  • same here, how to close it – junk Jan 28 '19 at 13:21
  • Did anyone figure out how to close this overlay? – bmacrevolution Feb 05 '19 at 17:03
  • I haven't found a solution to close the dialog opened in the way he opened in the answer, but if you open it with _showDialog, you can close it with Navigator.of(context).pop() – Fabrizio Apr 17 '19 at 14:45
  • where I should call Navigator.of(context).pop() ? @Fabrizio – Itoun Apr 19 '19 at 07:20
  • 2
    @RockerFlower @junk @bmacrevolution @Fabrizio @Itoun It is way easier to open the overlay with `showDialog()`. You'll only have to pass the `FunkyOverlay` inside the builder method. I have updated the example, it can now be closed, it is even less code than before ! :) Sorry for the inconvenience guys. – NiklasPor Apr 19 '19 at 08:07
  • how can it be close ? @Niklas – Itoun Apr 25 '19 at 10:02
  • @Itoun you can click on the background, or call `Navigator.pop()` :) – NiklasPor Apr 25 '19 at 10:10
32

Just use 'showGeneralDialog()' No need use extra lib or widget.

You can get more animatated dialog reference from This Link

void _openCustomDialog() {
    showGeneralDialog(barrierColor: Colors.black.withOpacity(0.5),
        transitionBuilder: (context, a1, a2, widget) {
          return Transform.scale(
            scale: a1.value,
            child: Opacity(
              opacity: a1.value,
              child: AlertDialog(
                shape: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(16.0)),
                title: Text('Hello!!'),
                content: Text('How are you?'),
              ),
            ),
          );
        },
        transitionDuration: Duration(milliseconds: 200),
        barrierDismissible: true,
        barrierLabel: '',
        context: context,
        pageBuilder: (context, animation1, animation2) {});
  }

enter image description here

Sanjayrajsinh
  • 15,014
  • 7
  • 73
  • 78
15

I tried to do the animation shown in your gif. Gonna post the code to help people who want it, its not perfect so if anyone wants to help improving it go for it.

How it looks:

enter image description here

Code:

import 'package:flutter/material.dart';
import 'package:angles/angles.dart';
import 'dart:math';
import 'dart:core';

class CheckAnimation extends StatefulWidget {
  final double size;
  final VoidCallback onComplete;

  CheckAnimation({this.size, this.onComplete});

  @override
  _CheckAnimationState createState() => _CheckAnimationState();
}

class _CheckAnimationState extends State<CheckAnimation>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> curve;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller =
        AnimationController(duration: Duration(seconds: 2), vsync: this);
    curve = CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);

    _controller.addListener(() {
      setState(() {});
      if(_controller.status == AnimationStatus.completed && widget.onComplete != null){
        widget.onComplete();
      }
    });
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.size ?? 100,
      width: widget.size ?? 100,
      color: Colors.transparent,
      child: CustomPaint(
        painter: CheckPainter(value: curve.value),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _controller.dispose();
    super.dispose();
  }
}

class CheckPainter extends CustomPainter {
  Paint _paint;
  double value;

  double _length;
  double _offset;
  double _secondOffset;
  double _startingAngle;

  CheckPainter({this.value}) {
    _paint = Paint()
      ..color = Colors.greenAccent
      ..strokeWidth = 5.0
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke;
    assert(value != null);

    _length = 60;
    _offset = 0;
    _startingAngle = 205;
  }

  @override
  void paint(Canvas canvas, Size size) {
    // Background canvas
    var rect = Offset(0, 0) & size;
    _paint.color = Colors.greenAccent.withOpacity(.05);

    double line1x1 = size.width / 2 +
        size.width * cos(Angle.fromDegrees(_startingAngle).radians) * .5;
    double line1y1 = size.height / 2 +
        size.height * sin(Angle.fromDegrees(_startingAngle).radians) * .5;
    double line1x2 = size.width * .45;
    double line1y2 = size.height * .65;

    double line2x1 =
        size.width / 2 + size.width * cos(Angle.fromDegrees(320).radians) * .35;
    double line2y1 = size.height / 2 +
        size.height * sin(Angle.fromDegrees(320).radians) * .35;

    canvas.drawArc(rect, Angle.fromDegrees(_startingAngle).radians,
        Angle.fromDegrees(360).radians, false, _paint);
    canvas.drawLine(Offset(line1x1, line1y1), Offset(line1x2, line1y2), _paint);
    canvas.drawLine(Offset(line2x1, line2y1), Offset(line1x2, line1y2), _paint);

    // animation painter

    double circleValue, checkValue;
    if (value < .5) {
      checkValue = 0;
      circleValue = value / .5;
    } else {
      checkValue = (value - .5) / .5;
      circleValue = 1;
    }

    _paint.color = const Color(0xff72d0c3);
    double firstAngle = _startingAngle + 360 * circleValue;

    canvas.drawArc(
        rect,
        Angle.fromDegrees(firstAngle).radians,
        Angle.fromDegrees(
                getSecondAngle(firstAngle, _length, _startingAngle + 360))
            .radians,
        false,
        _paint);

    double line1Value = 0, line2Value = 0;
    if (circleValue >= 1) {
      if (checkValue < .5) {
        line2Value = 0;
        line1Value = checkValue / .5;
      } else {
        line2Value = (checkValue - .5) / .5;
        line1Value = 1;
      }
    }

    double auxLine1x1 = (line1x2 - line1x1) * getMin(line1Value, .8);
    double auxLine1y1 =
        (((auxLine1x1) - line1x1) / (line1x2 - line1x1)) * (line1y2 - line1y1) +
            line1y1;

    if (_offset < 60) {
      auxLine1x1 = line1x1;
      auxLine1y1 = line1y1;
    }

    double auxLine1x2 = auxLine1x1 + _offset / 2;
    double auxLine1y2 =
        (((auxLine1x1 + _offset / 2) - line1x1) / (line1x2 - line1x1)) *
                (line1y2 - line1y1) +
            line1y1;

    if (checkIfPointHasCrossedLine(Offset(line1x2, line1y2),
        Offset(line2x1, line2y1), Offset(auxLine1x2, auxLine1y2))) {
      auxLine1x2 = line1x2;
      auxLine1y2 = line1y2;
    }

    if (_offset > 0) {
      canvas.drawLine(Offset(auxLine1x1, auxLine1y1),
          Offset(auxLine1x2, auxLine1y2), _paint);
    }

    // SECOND LINE

    double auxLine2x1 = (line2x1 - line1x2) * line2Value;
    double auxLine2y1 =
        ((((line2x1 - line1x2) * line2Value) - line1x2) / (line2x1 - line1x2)) *
                (line2y1 - line1y2) +
            line1y2;

    if (checkIfPointHasCrossedLine(Offset(line1x1, line1y1),
        Offset(line1x2, line1y2), Offset(auxLine2x1, auxLine2y1))) {
      auxLine2x1 = line1x2;
      auxLine2y1 = line1y2;
    }
    if (line2Value > 0) {
      canvas.drawLine(
          Offset(auxLine2x1, auxLine2y1),
          Offset(
              (line2x1 - line1x2) * line2Value + _offset * .75,
              ((((line2x1 - line1x2) * line2Value + _offset * .75) - line1x2) /
                          (line2x1 - line1x2)) *
                      (line2y1 - line1y2) +
                  line1y2),
          _paint);
    }
  }

  double getMax(double x, double y) {
    return (x > y) ? x : y;
  }

  double getMin(double x, double y) {
    return (x > y) ? y : x;
  }

  bool checkIfPointHasCrossedLine(Offset a, Offset b, Offset point) {
    return ((b.dx - a.dx) * (point.dy - a.dy) -
            (b.dy - a.dy) * (point.dx - a.dx)) >
        0;
  }

  double getSecondAngle(double angle, double plus, double max) {
    if (angle + plus > max) {
      _offset = angle + plus - max;
      return max - angle;
    } else {
      _offset = 0;
      return plus;
    }
  }

  @override
  bool shouldRepaint(CheckPainter old) {
    return old.value != value;
  }
}

I used angles package

Raul Mabe
  • 453
  • 5
  • 15
  • wow thanks Raul :), but when I use your code, I am getting a "dot" in the cross-point, where the checked line is turning. why? – Rebar Jul 19 '20 at 21:49
6

Whenever you want to show Dialog with some Animation, the best way is to use showGeneralDialog()

NOTE: ALL PARAMETERS MUST BE PROVIDED OTHERWISE SOME ERROR WILL OCCUR.

showGeneralDialog(
                barrierColor: Colors.black.withOpacity(0.5), //SHADOW EFFECT
                transitionBuilder: (context, a1, a2, widget) {
                  return Center(
                    child: Container(
                      height: 100.0 * a1.value,  // USE PROVIDED ANIMATION
                      width: 100.0 * a1.value,
                      color: Colors.blue,
                    ),
                  );
                },
                transitionDuration: Duration(milliseconds: 200), // DURATION FOR ANIMATION
                barrierDismissible: true,
                barrierLabel: 'LABEL',
                context: context,
                pageBuilder: (context, animation1, animation2) {
                  return Text('PAGE BUILDER');
                });
          }, child: Text('Show Dialog'),),

enter image description here

If you need more customization, then extend PopupRoute and create your own _DialogRoute<T> and showGeneralDialog()

EDIT

Edited answer of Niklas with functionality to close Overlay :)

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', theme: ThemeData(), home: Page());
  }
}

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton.icon(
            onPressed: () {
              OverlayEntry overlayEntry;
              overlayEntry = OverlayEntry(builder: (c) {
                return FunkyOverlay(onClose: () => overlayEntry.remove());
              });
              Overlay.of(context).insert(overlayEntry);
            },
            icon: Icon(Icons.message),
            label: Text("PopUp!")),
      ),
    );
  }
}

class FunkyOverlay extends StatefulWidget {
  final VoidCallback onClose;

  const FunkyOverlay({Key key, this.onClose}) : super(key: key);

  @override
  State<StatefulWidget> createState() => FunkyOverlayState();
}

class FunkyOverlayState extends State<FunkyOverlay>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> opacityAnimation;
  Animation<double> scaleAnimatoin;

  @override
  void initState() {
    super.initState();

    controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 450));
    opacityAnimation = Tween<double>(begin: 0.0, end: 0.4).animate(
        CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn));
    scaleAnimatoin =
        CurvedAnimation(parent: controller, curve: Curves.elasticInOut);

    controller.addListener(() {
      setState(() {});
    });

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.black.withOpacity(opacityAnimation.value),
      child: Center(
        child: ScaleTransition(
          scale: scaleAnimatoin,
          child: Container(
            decoration: ShapeDecoration(
                color: Colors.white,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(15.0))),
            child: Padding(
              padding: const EdgeInsets.all(50.0),
              child: OutlineButton(onPressed: widget.onClose, child: Text('Close!'),),
            ),
          ),
        ),
      ),
    );
  }
}
Mayur Prajapati
  • 597
  • 1
  • 6
  • 14
  • Where does the `Text('PAGE BUILDER');` get shown or used? Whenever `transitionBuilder` is used, it doesn't seem the `pageBuilder` is used, even though it's required. – Keith DC Feb 24 '21 at 05:40
  • Seems like it made optional now in the core lib, Keith – Mayur Prajapati Feb 24 '21 at 07:43
  • Unless I'm looking in the wrong place or reading it wrong (still new to Flutter): [showGeneralDialog function](https://api.flutter.dev/flutter/widgets/showGeneralDialog.html): `Future showGeneralDialog ({@required BuildContext context, @required RoutePageBuilder pageBuilder...})` – Keith DC Feb 24 '21 at 11:04
2

Add the below function in your code which shows a animated Dialog with Scale and Fade transition

  void _openCustomDialog(BuildContext context) {
    showGeneralDialog(
        barrierColor: Colors.black.withOpacity(0.5),
        transitionBuilder: (context, a1, a2, widget) {
          return ScaleTransition(
              scale: Tween<double>(begin: 0.5, end: 1.0).animate(a1),
              child: FadeTransition(
                opacity: Tween<double>(begin: 0.5, end: 1.0).animate(a1),
                child: const AboutPasteLog(
                  title: 'About',
                ),
              ));
        },
        transitionDuration: const Duration(milliseconds: 300),
        barrierDismissible: true,
        barrierLabel: '',
        context: context,
        pageBuilder: (context, animation1, animation2) {
          return Container();
        });
  }

And invoke it as below

  onPressed:(){
    _openCustomDialog(context);
  }

The final result

enter image description here

Mahesh Jamdade
  • 17,235
  • 8
  • 110
  • 131
0
import 'package:flutter/material.dart';

class AnimationModal extends StatefulWidget {

  const AnimationModal({super.key});

  @override
  State<AnimationModal> createState() => AnimationModalState();
}

class AnimationModalState extends State<AnimationModal> {

  @override

  Widget build(BuildContext context) {

  return Scaffold(
    body: Center(
      child: ElevatedButton(
        onPressed: () {
          print('okay');
          _openAnimatedModal(context);
        },
        style: ElevatedButton.styleFrom(
          backgroundColor: Colors.pink,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        textStyle: const TextStyle(
          fontSize: 15,
          fontWeight: 
          FontWeight.bold)
        ),
        child: const Text('Elevated Button'),
       ),),
     );
   }
 }


 void _openAnimatedModal(BuildContext context) {

 showGeneralDialog(
   context: context,
   pageBuilder: (context, animation1, animation2) {

    return Container();
   },

  transitionBuilder: (context, a1, a2, widget) {
  return ScaleTransition(
    scale: Tween<double>( begin: 0.5, end: 1.0 ).animate(a1),
    child: AlertDialog(
      // title: const Text('Modal Example'),
      content: Container(
        height: 100,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
                child: const Text(
                        'Do you want to exit the program?'
                       )
                ),
              Row(
                children: [
                  TextButton(
                    child: const Text(
                      'No',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 20,
                        color: Colors.red,
                      ),
                    ),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                  TextButton(
                    child: const Text(
                      'Yes',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 20,
                        color: Colors.green,
                      ),
                    ),
                    onPressed: () {
                     Navigator.of(context).pop();
                    },
                  ),
                ],
              ),
            ],
          ),
        ),
         shape: OutlineInputBorder(
           borderRadius: BorderRadius.circular(20),
           borderSide: BorderSide.none,
         ),
       ),
     );
   },
 );
}