flutter 實(shí)現(xiàn)點(diǎn)擊下拉欄微信右上角彈出窗功能
先看效果實(shí)現(xiàn)

需求分析
這個(gè)是使用 PopupRoute這個(gè)路由類進(jìn)行實(shí)現(xiàn)
大概原理就是利用PopupRpute這個(gè)類進(jìn)行改造,然后自定義一個(gè)頁面,頁面內(nèi)鑲嵌一個(gè)動(dòng)畫類,用來實(shí)現(xiàn)縮放動(dòng)畫
大概分為三部分,PopupRoute改造,彈出頁面設(shè)置,動(dòng)畫類設(shè)置。
為什么選擇PopupRoute?
可以鑲嵌在flutter本身的路由管理之中
也就是邏輯操作都是正常的頁面管理,可以手動(dòng)管理,也可以用路由返回直接關(guān)掉,不會(huì)影響原有頁面和布局
第一步,改造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;
}
第二步,新建一個(gè)彈窗頁面
頁面分兩部分

一個(gè)是頁面的背景,一個(gè)是頁面的內(nèi)容
注意,彈窗動(dòng)畫的代碼在下方
class Model extends StatefulWidget {
final double left; //距離左邊位置 彈窗的x軸定位
final double top; //距離上面位置 彈窗的y軸定位
final bool otherClose; //點(diǎn)擊背景關(guān)閉頁面
final Widget child; //傳入彈窗的樣式
final Function fun; // 把關(guān)閉的函數(shù)返回給父組件 參考vue的$emit
final Offset offset; // 彈窗動(dòng)畫的起點(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)畫 在下方,我把他分離 防止太長
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)閉頁面動(dòng)畫
Future closeModel() async {
await animateController.reverse();
Navigator.pop(context);
}
}
動(dòng)畫代碼
我是直接復(fù)制animate_do:^2.0.0 這個(gè)版本的ZoomIn的動(dòng)畫類
這個(gè)插件本身就是依賴flutter 自帶的動(dòng)畫來完成的,很簡潔,使用很方便,不過默認(rèn)構(gòu)造的時(shí)候沒有動(dòng)畫的啟動(dòng)方向,默認(rèn)是最中心。但是可以添加個(gè)參數(shù),我把源碼復(fù)制出來自己改造了一下。這個(gè)類在構(gòu)造的時(shí)候有個(gè)controller 參數(shù),類型的函數(shù),帶一個(gè)AnimationController的參數(shù)把控制器通過函數(shù)傳遞出去到Model類,可以在Model類里面進(jìn)行控制動(dòng)畫開啟和關(guān)閉后續(xù)我在Model類里面把動(dòng)畫關(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;
///把控制器通過函數(shù)傳遞出去,可以在父組件進(jìn)行控制
final Function(AnimationController) controller;
final bool manualTrigger;
final bool animate;
final double from;
///這是我自己寫的 起點(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,
),
);
},
);
}
}
最后頁面調(diào)用
我用stack類進(jìn)行堆疊組件,堆疊出上面箭頭
其實(shí)可以抽成一個(gè)方向設(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> {
///給獲取詳細(xì)信息的widget設(shè)置一個(gè)key
GlobalKey iconkey = GlobalKey();
///獲取位置,給后續(xù)彈窗設(shè)置位置
Offset iconOffset;
///獲取size 后續(xù)計(jì)算彈出位置
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: [],
),
),
),
);
}
///播放動(dòng)畫
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('開始'));
await closeModel();
print('結(jié)束');
},
))
.toList(),
),
),
),
],
),
);
}
}
然后就能實(shí)現(xiàn)我們的彈窗動(dòng)畫了,如果想要其他效果的動(dòng)畫,可以手動(dòng)替換動(dòng)畫類,或者自己手寫個(gè)新的最后我自己的項(xiàng)目修飾效果,還有demo的代碼代碼 倉庫地址:https://github.com/mannaoz/one

到此這篇關(guān)于flutter 實(shí)現(xiàn)點(diǎn)擊下拉欄微信右上角彈出窗功能的文章就介紹到這了,更多相關(guān)flutter彈出窗內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)讀取assets目錄下db文件的方法示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)讀取assets目錄下db文件的方法,結(jié)合實(shí)例形式分析了Android針對(duì)assets目錄下SQLite數(shù)據(jù)庫文件的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Android常用命令集錦(圖文并茂適應(yīng)于初學(xué)者)
大家好,今天我們要講的是android開發(fā)中,比較常用的名令集錦, 在我們開發(fā)中難免用到Android命令,有些確實(shí)命令確實(shí)很有用處,這也是我為什么總結(jié)這篇文章的原因了,希望對(duì)大家有所幫助2013-01-01
Android入門之計(jì)時(shí)器Chronometer的使用教程
Chronometer是一個(gè)簡單的定時(shí)器,你可以給它一個(gè)開始時(shí)間,并以此定時(shí)。本文將利用個(gè)簡單的示例為大家講解一下它的使用,感興趣的小伙伴可以嘗試一下2022-11-11
使用Thumbnails實(shí)現(xiàn)圖片指定大小壓縮
這篇文章主要為大家詳細(xì)介紹了使用Thumbnails實(shí)現(xiàn)圖片指定大小壓縮,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
Android 讀取sdcard上的圖片實(shí)例(必看)
下面小編就為大家?guī)硪黄狝ndroid 讀取sdcard上的圖片實(shí)例(必看)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
textView 添加超鏈接(兩種實(shí)現(xiàn)方式)
在textView添加超鏈接,有兩種方式,第一種通過HTML格式化你的網(wǎng)址,一種是設(shè)置autolink,讓系統(tǒng)自動(dòng)識(shí)別超鏈接,下面為大家介紹下這兩種方法的實(shí)現(xiàn)2013-06-06
Mac 下 Android Studio 不打印日志的解決辦法
這篇文章主要介紹了Mac 下 Android Studio 不打印日志的解決辦法的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10

