flutter 實現(xiàn)點擊下拉欄微信右上角彈出窗功能
先看效果實現(xiàn)
需求分析
這個是使用 PopupRoute這個路由類進行實現(xiàn)
大概原理就是利用PopupRpute這個類進行改造,然后自定義一個頁面,頁面內(nèi)鑲嵌一個動畫類,用來實現(xiàn)縮放動畫
大概分為三部分,PopupRoute改造,彈出頁面設(shè)置,動畫類設(shè)置。
為什么選擇PopupRoute?
可以鑲嵌在flutter本身的路由管理之中
也就是邏輯操作都是正常的頁面管理,可以手動管理,也可以用路由返回直接關(guān)掉,不會影響原有頁面和布局
第一步,改造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; }
第二步,新建一個彈窗頁面
頁面分兩部分
一個是頁面的背景,一個是頁面的內(nèi)容
注意,彈窗動畫的代碼在下方
class Model extends StatefulWidget { final double left; //距離左邊位置 彈窗的x軸定位 final double top; //距離上面位置 彈窗的y軸定位 final bool otherClose; //點擊背景關(guān)閉頁面 final Widget child; //傳入彈窗的樣式 final Function fun; // 把關(guān)閉的函數(shù)返回給父組件 參考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, ), ], ), ); } ///關(guān)閉頁面動畫 Future closeModel() async { await animateController.reverse(); Navigator.pop(context); } }
動畫代碼
我是直接復制animate_do:^2.0.0 這個版本的ZoomIn的動畫類
這個插件本身就是依賴flutter 自帶的動畫來完成的,很簡潔,使用很方便,不過默認構(gòu)造的時候沒有動畫的啟動方向,默認是最中心。但是可以添加個參數(shù),我把源碼復制出來自己改造了一下。這個類在構(gòu)造的時候有個controller 參數(shù),類型的函數(shù),帶一個AnimationController的參數(shù)把控制器通過函數(shù)傳遞出去到Model類,可以在Model類里面進行控制動畫開啟和關(guān)閉后續(xù)我在Model類里面把動畫關(guān)閉和返回退出PopupRoute層封裝成一個函數(shù) 傳遞到Model里面的fun參數(shù)里面返回出去可以在最外部進行組件通信,進而控制這些子組件
import 'package:flutter/material.dart'; class ZoomInOffset extends StatefulWidget { final Key key; final Widget child; final Duration duration; final Duration delay; ///把控制器通過函數(shù)傳遞出去,可以在父組件進行控制 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的可選構(gòu)造參數(shù),我們可以手動添加 return Transform.scale( origin: widget.offset, scale: fade.value, child: Opacity( opacity: opacity.value, child: widget.child, ), ); }, ); } }
最后頁面調(diào)用
我用stack類進行堆疊組件,堆疊出上面箭頭
其實可以抽成一個方向設(shè)置不過太麻煩了我沒寫,畢竟能用就行
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設(shè)置一個key GlobalKey iconkey = GlobalKey(); ///獲取位置,給后續(xù)彈窗設(shè)置位置 Offset iconOffset; ///獲取size 后續(xù)計算彈出位置 Size iconSize; ///接受彈窗類構(gòu)造成功傳遞來的關(guān)閉參數(shù) 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) { /// 設(shè)置傳入彈窗的高寬 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; }, ), ), ); } ///構(gòu)造傳入的widget Widget buildMenu() { ///構(gòu)造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), ), ), ), ///菜單內(nèi)容 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('結(jié)束'); }, )) .toList(), ), ), ), ], ), ); } }
然后就能實現(xiàn)我們的彈窗動畫了,如果想要其他效果的動畫,可以手動替換動畫類,或者自己手寫個新的最后我自己的項目修飾效果,還有demo的代碼代碼 倉庫地址:https://github.com/mannaoz/one
到此這篇關(guān)于flutter 實現(xiàn)點擊下拉欄微信右上角彈出窗功能的文章就介紹到這了,更多相關(guān)flutter彈出窗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android開發(fā)實現(xiàn)讀取assets目錄下db文件的方法示例
這篇文章主要介紹了Android開發(fā)實現(xiàn)讀取assets目錄下db文件的方法,結(jié)合實例形式分析了Android針對assets目錄下SQLite數(shù)據(jù)庫文件的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Mac 下 Android Studio 不打印日志的解決辦法
這篇文章主要介紹了Mac 下 Android Studio 不打印日志的解決辦法的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10