关于遮罩箭头菜单
2020-12-09 本文已影响0人
91阿生
先看效果图:
menu.gif创建的文件:
目录文件.png代码展示:
menu.dart
import 'package:flutter/material.dart';
import 'arrow_list.dart';
import 'fade_popup.dart';
import 'mask.dart';
class FCArrowMenuScreen extends StatefulWidget {
static const routeName = '/arrow_menu';
@override
_FCArrowMenuScreenState createState() => _FCArrowMenuScreenState();
}
class _FCArrowMenuScreenState extends State<FCArrowMenuScreen> {
GlobalKey<_FCArrowMenuScreenState> _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Top Arrow Menu')
),
body: Stack(
children: [
Positioned( // Positioned只能配合 Stack 使用
left: 150,
top: 150,
child: IconButton(
key: _globalKey,
icon: Icon(Icons.add_circle, size: 35, color: Colors.yellow[800]),
onPressed: () {
// 获取 IconButton 在屏幕上的坐标
// 1.先获取此组件, 利用组件绑定的key
// 2.组件自身的左上角 转屏幕坐标(零点)
RenderBox box = _globalKey.currentContext.findRenderObject();
Offset boxZeroOffset = box.localToGlobal(Offset.zero);
Size boxSize = box.size;
double menuX = boxZeroOffset.dx - FCArrowMenu.menuWidth * 0.5 + box.size.width * 0.5;
double menuY = boxZeroOffset.dy + boxSize.height;
Navigator.of(context).push(
/** 自定义动效路由*/
FCFadePopupRoute(child: FCMask(child: FCArrowMenu(), left: menuX, top: menuY))
/** 可使用 PageRouteBuilder路由: 系统自带动效路由;
PageRouteBuilder继承PopupRoute*/
// PageRouteBuilder(
// opaque: false,
// barrierDismissible: true,
// maintainState: true,
// transitionDuration: Duration(milliseconds: 250),
// pageBuilder: (context, animation, secondaryAnimation) {
// return FadeTransition(
// opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn)),
// child: FCMask(child: FCArrowMenu(), left: menuX, top: menuY),
// );
// })
);
}
),
)
]
),
);
}
}
fade_popup.dart 自定义动效
import 'package:flutter/material.dart';
class FCFadePopupRoute extends PopupRoute {
final Widget child;
FCFadePopupRoute({@required this.child});
@override
Color get barrierColor => null;
@override
String get barrierLabel => null;
@override
bool get barrierDismissible => true;
@override
bool get maintainState => true;
@override
Duration get transitionDuration => Duration(milliseconds: 250);
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animation, curve: Curves.easeIn)),
child: child,
);
}
}
mask.dart
import 'package:flutter/material.dart';
class FCMask extends StatelessWidget {
final Widget child;
final double left;
final double top;
FCMask({
@required this.child,
this.left,
this.top
});
@override
Widget build(BuildContext context) {
return Scaffold( // 这里也可以使用 Material(color: Colors.transparent, child: xxx)
backgroundColor: Colors.transparent,
body: GestureDetector(
onTap: ()=>Navigator.of(context).pop(),
child: Stack(
children: [
Container( // 底部全屏黑色透明背景
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.55)
),
Positioned(
top: top,
left: left,
child: child
)
],
)
),
);
}
}
arrow_list.dart
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
// cell显示内容数据模型
class MenuItem {
final Widget leading;
final String title;
MenuItem({
this.leading,
this.title
});
}
// items数据源
List<MenuItem> items = [
MenuItem(leading: Icon(Icons.warning_amber_rounded, color: Colors.white, size: 28), title: "异常提示"),
MenuItem(leading: Icon(Icons.update_rounded, color: Colors.white, size: 28), title: "更新信息"),
MenuItem(leading: Icon(Icons.message_rounded, color: Colors.white, size: 28), title: "回复信息")
];
class FCArrowMenu extends StatelessWidget {
static double menuWidth = MediaQueryData.fromWindow(ui.window).size.width *0.35;
@override
Widget build(BuildContext context) {
return Container(
width: menuWidth,
child: Column(
children: [
// Image.asset('assets/images/img_up_arrow.png', width: 25.0, height: 13.4),
FCTrigon(), // 自定义绘制箭头
Container(
constraints: BoxConstraints(maxHeight: 45.0*5), // 设置最大容器高度
padding: EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.55),
borderRadius: BorderRadius.circular(5)
),
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: items.length,
itemBuilder: (context, index) {
Widget leading = items[index].leading;
String title = items[index].title;
return GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
leading,
SizedBox(width: 8),
Expanded(
child: Text(title, style: TextStyle(
fontSize: 17,
color: Colors.white,
fontWeight: FontWeight.bold
)),
)
],
),
),
onTap: () {
print("$title");
},
);
},
),
)
],
),
);
}
}
/* 绘制三角形 */
class FCTrigon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ClipPath(
clipper: FCTrigonPath(),
child: Container(
width: 25.0,
height: 13.4,
color: Colors.black.withOpacity(0.55),
),
);
}
}
class FCTrigonPath extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, size.height);
path.lineTo(size.width * 0.5, 0);
path.lineTo(size.width, size.height);
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}