The flyer realizes the pop-up window. Click the pop-up window in the upper right corner of wechat in the drop-down bar

Time:2021-11-13

See the effect first

 

 

 

 

requirement analysis

 

This is implemented using the popuproute routing class

 

The general principle is to use the popuprpute class to transform, and then customize a page. An animation class is embedded in the page to realize zoom animation

 

It is roughly divided into three parts: popuproute transformation, pop-up page setting and animation setting.

 

Why popuproute?

 

It can be embedded in the routing management of the shuttle itself

 

That is, the logical operations are normal page management, which can be managed manually or turned off directly by routing return, without affecting the original page and layout

 

The first step is to transform the popuproute class

import ‘package:flutter/material.dart’;

class Popup extends PopupRoute {
  final Duration _duration = Duration(milliseconds: 300);
  Widget child;

  Popup({@required this.child});

  @override
  Color get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation animation,
      Animation secondaryAnimation) {
    return child;
  }

  @override
  Duration get transitionDuration => _duration;
}

 

Step 2: create a pop-up page

The page is divided into two parts

 

 

 

One is the background of the page and the other is the content of the page

Note that the pop-up animation code is below

class Model extends StatefulWidget {
final   double   left;  // Left position   X-axis positioning of pop-up window
final   double   top;  // Distance above   Y-axis positioning of pop-up window
final   bool   otherClose;  // Click background to close the page
final   Widget   child;  // Incoming pop-up style
final   Function   fun;  //  Return the closed function to the parent component   Reference Vue’s $emit
final   Offset   offset;  //  Start point of pop-up animation

  Model({
    @required this.child,
    this.left = 0,
    this.top = 0,
    this.otherClose = false,
    this.fun,
    this.offset,
  });

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

class _ModelState extends State {
  AnimationController animateController;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: Stack(
        children: [
          Positioned(
            child: GestureDetector(
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                color: Colors.transparent,
              ),
              onTap: () async {
                if (widget.otherClose) {
                } else {
                  closeModel();
                }
              },
            ),
          ),
          Positioned(
///   This is the pop-up animation at the bottom. I separate it to prevent it from being too long
            child: ZoomInOffset(
              duration: Duration(milliseconds: 180),
              offset: widget.offset,
              controller: (controller) {
                animateController = controller;
                widget.fun(closeModel);
              },
              child: widget.child,
            ),
            left: widget.left,
            top: widget.top,
          ),
        ],
      ),
    );
  }

///Turn off page animation
  Future closeModel() async {
    await animateController.reverse();
    Navigator.pop(context);
  }
}

 

Animation code

I’m a direct copy    animate_ do:  ^ 2.0.0 this version of zoomin’s animation class

The plug-in itself relies on the animation provided by fluent. It is very concise and easy to use,
However, there is no animation start direction during the default construction, and the default is the center.
But you can add a parameter. I copied the source code and modified it myself.
This class has a   Controller parameter, function of type, with an animationcontroller parameter

Pass the controller to the model class through the function, and you can control the animation on and off in the model class
 
Later, in the model class, I encapsulated the animation close and return exit popuproute layer into a function, passed it to the fun parameter in the model and returned it
These subcomponents can be controlled by component communication at the outermost
 
import ‘package:flutter/material.dart’;

class ZoomInOffset extends StatefulWidget {
  final Key key;
  final Widget child;
  final Duration duration;
  final Duration delay;

///The controller is passed through the function, which can be controlled in the parent component
  final Function(AnimationController) controller;
  final bool manualTrigger;
  final bool animate;
  final double from;

///I wrote it myself   starting point
  final Offset offset;

  ZoomInOffset(
      {this.key,
      this.child,
      this.duration = const Duration(milliseconds: 500),
      this.delay = const Duration(milliseconds: 0),
      this.controller,
      this.manualTrigger = false,
      this.animate = true,
      this.offset,
      this.from = 1.0})
      : super(key: key) {
    if (manualTrigger == true && controller == null) {
      throw FlutterError(‘If you want to use manualTrigger:true, \n\n’
          ‘Then you must provide the controller property, that is a callback like:\n\n’
          ‘ ( controller: AnimationController) => yourController = controller \n\n’);
    }
  }

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

/// State class, where the magic happens
class _ZoomInState extends State
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  bool disposed = false;
  Animation fade;
  Animation opacity;

  @override
  void dispose() async {
    disposed = true;
    controller.dispose();
    super.dispose();
  }

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

    controller = AnimationController(duration: widget.duration, vsync: this);
    fade = Tween(begin: 0.0, end: widget.from)
        .animate(CurvedAnimation(curve: Curves.easeOut, parent: controller));

    opacity = Tween(begin: 0.0, end: 1)
        .animate(CurvedAnimation(parent: controller, curve: Interval(0, 0.65)));

    if (!widget.manualTrigger && widget.animate) {
      Future.delayed(widget.delay, () {
        if (!disposed) {
          controller?.forward();
        }
      });
    }

    if (widget.controller is Function) {
      widget.controller(controller);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (widget.animate && widget.delay.inMilliseconds == 0) {
      controller?.forward();
    }

    return AnimatedBuilder(
      animation: fade,
      builder: (BuildContext context, Widget child) {
///    This transform has optional construction parameters of origin, which we can add manually
        return Transform.scale(
          origin: widget.offset,
          scale: fade.value,
          child: Opacity(
            opacity: opacity.value,
            child: widget.child,
          ),
        );
      },
    );
  }
}

 

Last page call

I use the stack class to stack components and stack the arrows above

In fact, it can be set in one direction, but it’s too troublesome. I didn’t write it. After all, it’s OK to use it

import ‘package:flutter/material.dart’;
import ‘package:one/widget/Model.dart’;
import ‘package:one/widget/Popup.dart’;

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {
///Set a key for the widget that gets the details
  GlobalKey iconkey = GlobalKey();

///Obtain the position and set the position for the subsequent pop-up window
  Offset iconOffset;

///Get size   Pop up position for subsequent calculation
  Size iconSize;

///Accept the closing parameters successfully passed by the pop-up class construction
  Function closeModel;

  @override
  Widget build(BuildContext context) {
///Wait for widget initialization to complete
    WidgetsBinding.instance.addPostFrameCallback((duration) {
///Get the location of the widget through the key
      RenderBox box = iconkey.currentContext.findRenderObject();

///Gets the height and width of the widget
      iconSize = box.size;

///Get location
      iconOffset = box.localToGlobal(Offset.zero);
    });

    return MaterialApp(
      home: Builder(
        builder: (context) => Scaffold(
          appBar: AppBar(
            actions: [
              IconButton(
                key: iconkey,
                icon: Icon(
                  Icons.favorite,
                  color: Colors.red,
                ),
                onPressed: () {
                  showModel(context);
                },
              ),
            ],
          ),
          body: Column(
            children: [],
          ),
        ),
      ),
    );
  }

///Play animation
  void showModel(BuildContext context) {
///   Sets the height and width of the incoming pop-up window
    double _width = 130;
    double _height = 230;

    Navigator.push(
      context,
      Popup(
        child: Model(
          left: iconOffset.dx – _width + iconSize.width / 1.2,
          top: iconOffset.dy + iconSize.height / 1.3,
          offset: Offset(_width / 2, -_height / 2),
          child: Container(
            width: _width,
            height: _height,
            child: buildMenu(),
          ),
          fun: (close) {
            closeModel = close;
          },
        ),
      ),
    );
  }

///Construct the incoming widget
  Widget buildMenu() {
///Construct list
    List _list = [1, 2, 3, 4, 5];

    return Container(
      height: 160,
      width: 230,
      child: Stack(
        children: [
          Positioned(
            right: 4,
            top: 17,
            child: Container(
              width: 20,
              height: 20,
              transform: Matrix4.rotationZ(45 * 3.14 / 180),
              decoration: BoxDecoration(
                color: Color.fromRGBO(46, 53, 61, 1),
                borderRadius: BorderRadius.circular(5),
              ),
            ),
          ),

///Menu content
          Positioned(
            bottom: 0,
            child: Container(
              padding: EdgeInsets.only(
                top: 20,
                bottom: 20,
                left: 10,
                right: 10,
              ),
              width: 130,
              height: 200,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                color: Color.fromRGBO(46, 53, 61, 1),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: _list
                    .map((e) => InkWell(
                          child: Container(
                            width: double.infinity,
                            alignment: Alignment.center,
                            child: Text(
‘This should be the option ${e.tostring()} ‘,
                              style: TextStyle(
                                color: Colors.white70,
                                fontSize: 14,
                              ),
                            ),
                          ),
                          onTap: () async {
Print (‘This is the option ${e.tostring()} ‘) clicked;
                            await Future.delayed(Duration(milliseconds: 500))
.then((value)  =>  Print (‘Start ‘);
                            await closeModel();
Print (‘end ‘);
                          },
                        ))
                    .toList(),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

 

Then we can realize our pop-up animation. If you want animation with other effects, you can manually replace the animation class or write a new one by yourself
 
Finally, my own project modification effect, as well as the demo code
Code warehouse address: https://github.com/mannaoz/one