您好,登录后才能下订单哦!
小编给大家分享一下flutter如何实现点击下拉栏微信右上角弹出窗功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
先看效果实现
这个是使用 PopupRoute这个路由类进行实现
大概原理就是利用PopupRpute这个类进行改造,然后自定义一个页面,页面内镶嵌一个动画类,用来实现缩放动画
大概分为三部分,PopupRoute改造,弹出页面设置,动画类设置。
为什么选择PopupRoute?
可以镶嵌在flutter本身的路由管理之中
也就是逻辑操作都是正常的页面管理,可以手动管理,也可以用路由返回直接关掉,不会影响原有页面和布局
第一步,改造PopupRoute类
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<double> animation, Animation<double> secondaryAnimation) { return child; } @override Duration get transitionDuration => _duration; }
第二步,新建一个弹窗页面
页面分两部分
一个是页面的背景,一个是页面的内容
注意,弹窗动画的代码在下方
class Model extends StatefulWidget { final double left; //距离左边位置 弹窗的x轴定位 final double top; //距离上面位置 弹窗的y轴定位 final bool otherClose; //点击背景关闭页面 final Widget child; //传入弹窗的样式 final Function fun; // 把关闭的函数返回给父组件 参考vue的$emit final Offset offset; // 弹窗动画的起点 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<Model> { AnimationController animateController; @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: Stack( children: <Widget>[ 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( /// 这个是弹窗动画 在下方,我把他分离 防止太长 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, ), ], ), ); } ///关闭页面动画 Future closeModel() async { await animateController.reverse(); Navigator.pop(context); } }
我是直接复制animate_do:^2.0.0 这个版本的ZoomIn的动画类
这个插件本身就是依赖flutter 自带的动画来完成的,很简洁,使用很方便,不过默认构造的时候没有动画的启动方向,默认是最中心。但是可以添加个参数,我把源码复制出来自己改造了一下。这个类在构造的时候有个controller 参数,类型的函数,带一个AnimationController的参数把控制器通过函数传递出去到Model类,可以在Model类里面进行控制动画开启和关闭后续我在Model类里面把动画关闭和返回退出PopupRoute层封装成一个函数 传递到Model里面的fun参数里面返回出去可以在最外部进行组件通信,进而控制这些子组件
import 'package:flutter/material.dart'; class ZoomInOffset extends StatefulWidget { final Key key; final Widget child; final Duration duration; final Duration delay; ///把控制器通过函数传递出去,可以在父组件进行控制 final Function(AnimationController) controller; final bool manualTrigger; final bool animate; final double from; ///这是我自己写的 起点 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<ZoomInOffset> with SingleTickerProviderStateMixin { AnimationController controller; bool disposed = false; Animation<double> fade; Animation<double> 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<double>(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) { /// 这个transform有origin的可选构造参数,我们可以手动添加 return Transform.scale( origin: widget.offset, scale: fade.value, child: Opacity( opacity: opacity.value, child: widget.child, ), ); }, ); } }
最后页面调用
我用stack类进行堆叠组件,堆叠出上面箭头
其实可以抽成一个方向设置不过太麻烦了我没写,毕竟能用就行
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<MyApp> { ///给获取详细信息的widget设置一个key GlobalKey iconkey = GlobalKey(); ///获取位置,给后续弹窗设置位置 Offset iconOffset; ///获取size 后续计算弹出位置 Size iconSize; ///接受弹窗类构造成功传递来的关闭参数 Function closeModel; @override Widget build(BuildContext context) { ///等待widget初始化完成 WidgetsBinding.instance.addPostFrameCallback((duration) { ///通过key获取到widget的位置 RenderBox box = iconkey.currentContext.findRenderObject(); ///获取widget的高宽 iconSize = box.size; ///获取位置 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: [], ), ), ), ); } ///播放动画 void showModel(BuildContext context) { /// 设置传入弹窗的高宽 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; }, ), ), ); } ///构造传入的widget Widget buildMenu() { ///构造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), ), ), ), ///菜单内容 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<Widget>((e) => InkWell( child: Container( width: double.infinity, alignment: Alignment.center, child: Text( '这应该是选项${e.toString()}', style: TextStyle( color: Colors.white70, fontSize: 14, ), ), ), onTap: () async { print('这是点击了选项${e.toString()}'); await Future.delayed(Duration(milliseconds: 500)) .then((value) => print('开始')); await closeModel(); print('结束'); }, )) .toList(), ), ), ), ], ), ); } }
然后就能实现我们的弹窗动画了,如果想要其他效果的动画,可以手动替换动画类,或者自己手写个新的最后我自己的项目修饰效果。
以上是“flutter如何实现点击下拉栏微信右上角弹出窗功能”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。