您好,登錄后才能下訂單哦!
小編給大家分享一下flutter如何實(shí)現(xiàn)點(diǎn)擊下拉欄微信右上角彈出窗功能,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
先看效果實(shí)現(xiàn)
這個(gè)是使用 PopupRoute這個(gè)路由類(lèi)進(jìn)行實(shí)現(xiàn)
大概原理就是利用PopupRpute這個(gè)類(lèi)進(jìn)行改造,然后自定義一個(gè)頁(yè)面,頁(yè)面內(nèi)鑲嵌一個(gè)動(dòng)畫(huà)類(lèi),用來(lái)實(shí)現(xiàn)縮放動(dòng)畫(huà)
大概分為三部分,PopupRoute改造,彈出頁(yè)面設(shè)置,動(dòng)畫(huà)類(lèi)設(shè)置。
為什么選擇PopupRoute?
可以鑲嵌在flutter本身的路由管理之中
也就是邏輯操作都是正常的頁(yè)面管理,可以手動(dòng)管理,也可以用路由返回直接關(guān)掉,不會(huì)影響原有頁(yè)面和布局
第一步,改造PopupRoute類(lèi)
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; }
第二步,新建一個(gè)彈窗頁(yè)面
頁(yè)面分兩部分
一個(gè)是頁(yè)面的背景,一個(gè)是頁(yè)面的內(nèi)容
注意,彈窗動(dòng)畫(huà)的代碼在下方
class Model extends StatefulWidget { final double left; //距離左邊位置 彈窗的x軸定位 final double top; //距離上面位置 彈窗的y軸定位 final bool otherClose; //點(diǎn)擊背景關(guān)閉頁(yè)面 final Widget child; //傳入彈窗的樣式 final Function fun; // 把關(guān)閉的函數(shù)返回給父組件 參考vue的$emit final Offset offset; // 彈窗動(dòng)畫(huà)的起點(diǎn) 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( /// 這個(gè)是彈窗動(dòng)畫(huà) 在下方,我把他分離 防止太長(zhǎng) 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)閉頁(yè)面動(dòng)畫(huà) Future closeModel() async { await animateController.reverse(); Navigator.pop(context); } }
我是直接復(fù)制animate_do:^2.0.0 這個(gè)版本的ZoomIn的動(dòng)畫(huà)類(lèi)
這個(gè)插件本身就是依賴flutter 自帶的動(dòng)畫(huà)來(lái)完成的,很簡(jiǎn)潔,使用很方便,不過(guò)默認(rèn)構(gòu)造的時(shí)候沒(méi)有動(dòng)畫(huà)的啟動(dòng)方向,默認(rèn)是最中心。但是可以添加個(gè)參數(shù),我把源碼復(fù)制出來(lái)自己改造了一下。這個(gè)類(lèi)在構(gòu)造的時(shí)候有個(gè)controller 參數(shù),類(lèi)型的函數(shù),帶一個(gè)AnimationController的參數(shù)把控制器通過(guò)函數(shù)傳遞出去到Model類(lèi),可以在Model類(lèi)里面進(jìn)行控制動(dòng)畫(huà)開(kāi)啟和關(guān)閉后續(xù)我在Model類(lèi)里面把動(dòng)畫(huà)關(guān)閉和返回退出PopupRoute層封裝成一個(gè)函數(shù) 傳遞到Model里面的fun參數(shù)里面返回出去可以在最外部進(jìn)行組件通信,進(jìn)而控制這些子組件
import 'package:flutter/material.dart'; class ZoomInOffset extends StatefulWidget { final Key key; final Widget child; final Duration duration; final Duration delay; ///把控制器通過(guò)函數(shù)傳遞出去,可以在父組件進(jìn)行控制 final Function(AnimationController) controller; final bool manualTrigger; final bool animate; final double from; ///這是我自己寫(xiě)的 起點(diǎn) 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) { /// 這個(gè)transform有origin的可選構(gòu)造參數(shù),我們可以手動(dòng)添加 return Transform.scale( origin: widget.offset, scale: fade.value, child: Opacity( opacity: opacity.value, child: widget.child, ), ); }, ); } }
最后頁(yè)面調(diào)用
我用stack類(lèi)進(jìn)行堆疊組件,堆疊出上面箭頭
其實(shí)可以抽成一個(gè)方向設(shè)置不過(guò)太麻煩了我沒(méi)寫(xiě),畢竟能用就行
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> { ///給獲取詳細(xì)信息的widget設(shè)置一個(gè)key GlobalKey iconkey = GlobalKey(); ///獲取位置,給后續(xù)彈窗設(shè)置位置 Offset iconOffset; ///獲取size 后續(xù)計(jì)算彈出位置 Size iconSize; ///接受彈窗類(lèi)構(gòu)造成功傳遞來(lái)的關(guān)閉參數(shù) Function closeModel; @override Widget build(BuildContext context) { ///等待widget初始化完成 WidgetsBinding.instance.addPostFrameCallback((duration) { ///通過(guò)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: [], ), ), ), ); } ///播放動(dòng)畫(huà) 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( '這應(yīng)該是選項(xiàng)${e.toString()}', style: TextStyle( color: Colors.white70, fontSize: 14, ), ), ), onTap: () async { print('這是點(diǎn)擊了選項(xiàng)${e.toString()}'); await Future.delayed(Duration(milliseconds: 500)) .then((value) => print('開(kāi)始')); await closeModel(); print('結(jié)束'); }, )) .toList(), ), ), ), ], ), ); } }
然后就能實(shí)現(xiàn)我們的彈窗動(dòng)畫(huà)了,如果想要其他效果的動(dòng)畫(huà),可以手動(dòng)替換動(dòng)畫(huà)類(lèi),或者自己手寫(xiě)個(gè)新的最后我自己的項(xiàng)目修飾效果。
以上是“flutter如何實(shí)現(xiàn)點(diǎn)擊下拉欄微信右上角彈出窗功能”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。