Flutter custom animated dialog

前端 未结 4 573
轻奢々
轻奢々 2020-12-29 09:09

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

相关标签:
4条回答
  • 2020-12-29 09:16

    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:

    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

    0 讨论(0)
  • 2020-12-29 09:21

    Just useshowGeneralDialog() 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) {});
      }
    

    0 讨论(0)
  • 2020-12-29 09:24

    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.

    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!"),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    0 讨论(0)
  • 2020-12-29 09:38

    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'),),
    

    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!'),),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    0 讨论(0)
提交回复
热议问题