123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- import 'package:flutter/material.dart';
- import 'package:flyinsono/lab/color/lab_colors.dart';
- import 'package:flyinsono/lab/components/operate_button/more_operate_button.dart';
- import 'package:flyinsono/lab/style/lab_box_decoration.dart';
- /// 更多功能扩展菜单
- class MoreOperateMenu extends StatefulWidget {
- const MoreOperateMenu({
- super.key,
- required this.buttons,
- }) : super();
- final List<MoreOperateButton> buttons;
- static const defaultTextStyle = TextStyle(
- color: LabColors.text200,
- fontSize: 12,
- );
- static const defaultDecoration = BoxDecoration(
- color: LabColors.base800,
- borderRadius: BorderRadius.all(Radius.circular(4)),
- );
- @override
- State<MoreOperateMenu> createState() => MoreOperateMenuState();
- static MoreOperateMenuState? maybeOf(BuildContext context) {
- // assert(context);
- return context.findAncestorStateOfType<MoreOperateMenuState>();
- }
- }
- class MoreOperateMenuState extends State<MoreOperateMenu> {
- OverlayEntry? _entry;
- bool _isHoverMore = false;
- /// 显示 Overlay
- void _showOverlay() {
- if (_entry != null) {
- return;
- }
- _createNewEntry();
- }
- /// 隐藏 Overlay
- void _hideOverlay() {
- _entry?.remove();
- _entry = null;
- }
- /// 创建新的 OverlayEntry
- void _createNewEntry() {
- final OverlayState overlayState = Overlay.of(
- context,
- debugRequiredFor: widget,
- );
- final RenderBox target = context.findRenderObject()! as RenderBox;
- final RenderBox targetConatiner =
- overlayState.context.findRenderObject() as RenderBox;
- final Offset targetOffset = target.localToGlobal(
- target.size.center(Offset.zero),
- ancestor: overlayState.context.findRenderObject(),
- );
- final Offset overlayOffset =
- _countOffset(targetConatiner.size, target.size, targetOffset);
- final Offset edgeOffset =
- _edgeCorrection(targetConatiner.size, target.size, overlayOffset);
- final Widget overlay = _buildOverlay(edgeOffset);
- _entry = OverlayEntry(builder: (BuildContext context) => overlay);
- _listenButtonClicked();
- overlayState.insert(_entry!);
- }
- /// 监听按钮事件来关闭 Overlay
- void _listenButtonClicked() {
- for (final button in widget.buttons) {
- button.clicked.removeListener(_closeMoreMenu);
- }
- for (final button in widget.buttons) {
- button.clicked.addListener(_closeMoreMenu);
- }
- }
- /// 创建 Overlay 组件
- Widget _buildOverlay(
- Offset offset,
- ) {
- return Directionality(
- textDirection: Directionality.of(context),
- child: Positioned.directional(
- top: offset.dy,
- start: offset.dx,
- textDirection: Directionality.of(context),
- child: MouseRegion(
- onEnter: _handleMouseEnterMenu,
- onExit: _handleMouseExitMenu,
- child: Container(
- padding: EdgeInsets.only(top: 5),
- child: Container(
- decoration: LabBoxDecoration.base.copyWith(
- color: LabColors.base300,
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.1),
- blurRadius: 2,
- spreadRadius: 1,
- ),
- ],
- ),
- padding: EdgeInsets.all(5),
- child: SizedBox(
- width: 100,
- child: Column(
- children: widget.buttons,
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
- /// 计算位置布局
- Offset _countOffset(
- Size containerSize, Size targetSize, Offset targetOffset) {
- final double x = targetOffset.dx;
- final double y = targetOffset.dy;
- final double w = targetSize.width;
- final double h = targetSize.height;
- return Offset(x - w / 2, y + h / 2);
- }
- /// 边缘矫正
- Offset _edgeCorrection(
- Size containerSize, Size targetSize, Offset targetOffset) {
- final double x = targetOffset.dx;
- final double y = targetOffset.dy;
- final double w = targetSize.width;
- final double h = targetSize.height;
- final double cw = containerSize.width;
- final double ch = containerSize.height;
- if (x < 0) {
- return Offset(0, y);
- }
- if (x + w > cw) {
- return Offset(cw - w, y);
- }
- if (y < 0) {
- return Offset(x, 0);
- }
- if (y + h > ch) {
- return Offset(x, ch - h);
- }
- return targetOffset;
- }
- @override
- Widget build(BuildContext context) {
- Color _colorMore = _isHoverMore ? LabColors.base400 : Colors.transparent;
- return MouseRegion(
- cursor: SystemMouseCursors.click,
- onEnter: _handleMouseEnterMoreButton,
- onExit: _handleMouseExitMoreButton,
- child: Container(
- decoration: BoxDecoration(
- color: _colorMore,
- borderRadius: BorderRadius.circular(5),
- ),
- child: Container(
- width: 80,
- child: Icon(
- Icons.keyboard_arrow_down_rounded,
- size: 20,
- color: LabColors.base700,
- ),
- ),
- ),
- );
- }
- void _handleMouseEnterMoreButton(PointerEvent details) {
- _showOverlay();
- setState(() {
- _isHoverMore = true;
- });
- }
- void _handleMouseExitMoreButton(PointerEvent details) {
- setState(() {
- _isHoverMore = false;
- });
- // 如果下一帧不在菜单内,则隐藏 Overlay
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- if (!_isHoverMore) {
- _hideOverlay();
- setState(() {
- _isHoverMore = false;
- });
- }
- });
- }
- void _handleMouseEnterMenu(PointerEvent details) {
- setState(() {
- _isHoverMore = true;
- });
- }
- void _handleMouseExitMenu(PointerEvent details) {
- _hideOverlay();
- setState(() {
- _isHoverMore = false;
- });
- }
- void _closeMoreMenu() {
- _hideOverlay();
- setState(() {
- _isHoverMore = false;
- });
- }
- /// 组件入参更新时,如果 disable 为 true,则隐藏 Overlay
- @override
- void didUpdateWidget(MoreOperateMenu oldWidget) {
- super.didUpdateWidget(oldWidget);
- }
- @override
- void dispose() {
- super.dispose();
- if (_entry != null) {
- _entry?.remove();
- _entry = null;
- }
- }
- }
|