关于Flutter Desktop菜单的优化

关于Flutter Desktop菜单的优化

在Flutter中有一个显示菜单的方法showMenu它可以显示一个菜单.

以此代码为例

    showMenu(
      context: context,
      position: RelativeRect.fromLTRB(100.0, 200.0, 100.0, 200.0), // 在屏幕的哪个位置弹出
      items: <PopupMenuEntry<String>>[
        PopupMenuItem<String>(
          value: 'Option 1',
          child: Text('Option 1'),
        ),
        PopupMenuItem<String>(
          value: 'Option 2',
          child: Text('Option 2'),
        ),
      ],
    ).then((value) {
      if (value != null) {
        setState(() {
          _selection = value;
        });
      }
    });

可以获得如下效果

表面看起来很完美, 但是它有个小缺陷, 就是点开菜单后我们没办法操作其它按钮, 比如我们点击Show Toast是没有效果的, 而是会先让菜单消失, 然后再次点击Show Toast才有效果.

但是这在桌面端上操作并不是很友好, 我们的操作系统和IDE的右键菜单都是不会影响其它事件的.

比如Android Studio的菜单效果是这样的, 它并不需要先消失菜单才能执行其它操作.

如果我们让Flutter实现菜单不影响其它按钮的点击那该如何实现呢?

首先分析Flutter自带的showMenu为什么不能穿透? 这是因为Flutter的showMenu其实也是在Overlay中插入的OverlayEntry, 虽然没有内容的地方是透明的, 但是这些透明的地方无法穿透, 有点击事件监听.

那么我们是不是只要创建一个OverlayEntry并且让透明区域可以穿透过去就行呢?

其实确实可以, 但是这样有个缺陷, 我们的菜单并不知道你点击了其它区域, 然后它不会自动消失.

如果我们想完美实现, 则我们需要自定义Widget.

我们可以实现这么一个Widget

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

typedef ChildOffsetGetter = Offset Function(Size parentSize, Size childSize);

typedef OnOutsideClick = void Function(Offset position);

class TransParentContainer extends SingleChildRenderObjectWidget {
  final ChildOffsetGetter? childOffsetGetter;
  final OnOutsideClick? onOutsideClick;

  const TransParentContainer({super.key, required super.child, this.childOffsetGetter, this.onOutsideClick});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return TransParentRenderBox(childOffsetGetter, onOutsideClick);
  }

  @override
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
    super.updateRenderObject(context, renderObject);
    final ro = (renderObject as TransParentRenderBox);
    ro.childOffsetGetter = childOffsetGetter;
    ro.onOutsideClick = onOutsideClick;
  }
}

class TransParentRenderBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
  ChildOffsetGetter? childOffsetGetter;
  OnOutsideClick? onOutsideClick;

  TransParentRenderBox(this.childOffsetGetter, this.onOutsideClick);

  @override
  void performLayout() {
    child!.layout(constraints, parentUsesSize: true);
    size = Size(constraints.maxWidth, constraints.maxHeight);
    final getter = childOffsetGetter ?? defaultCenterOffsetGetter;
    _getChildParentData().offset = getter(size, child!.size);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    //绘制子控件
    context.paintChild(child!, offset + _getChildParentData().offset);
  }

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    if (size.contains(position)) {
      if (child!.hitTest(result, position: position - _getChildParentData().offset)) {
        return true;
      } else {
        result.add(BoxHitTestEntry(this, position));
        return false;
      }
    }
    return false;
  }

  @override
  void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
    if (event is PointerDownEvent) {
      onOutsideClick?.call(event.position);
    }
  }

  Offset defaultCenterOffsetGetter(parentSize, childSize) {
    return Offset((parentSize.width - childSize.width) / 2, (parentSize.height - childSize.height) / 2);
  }

  BoxParentData _getChildParentData() {
    return child!.parentData as BoxParentData;
  }
}

这里原理主要是在hitTest, 这个组件在点击事件不在子控件中时依然会对事件进行监听, 但是不会截断事件. 这样能实现既不影响其它按钮的点击, 菜单也会自动消失.

这里不再赘述自定义Widget相关的内容, 可以参考前一篇Flutter自定义View基础,重写SingleChildRenderObjectWidget

这里我们添加另一个展示菜单的方法

  void _showMenu2(BuildContext context) {
    var state = Overlay.of(context);
    late OverlayEntry entry;
    entry = OverlayEntry(builder: (c) {
      return Container(
        alignment: Alignment.topLeft,
        child: TransParentContainer(
          childOffsetGetter: (ps, cs) {
            return Offset(100, 200);
          },
          onOutsideClick: (position) {
            entry.remove();
          },
          child: Wrap(
            children: [
              Material(
                elevation: 5.0,
                shadowColor: Colors.grey,
                color: Colors.white,
                child: Column(
                  children: [
                    Padding(
                      padding: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 4),
                      child: Text(
                        "Menu Item 1",
                        style: TextStyle(fontSize: 24),
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 4),
                      child: Text(
                        "Menu Item 1",
                        style: TextStyle(fontSize: 24),
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 4),
                      child: Text(
                        "Menu Item 1",
                        style: TextStyle(fontSize: 24),
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
      );
    });
    state.insert(entry);
  }

最后再看一下效果对比

其中Open Menu2使用的新代码, 展示菜单后可以正常点击其它按钮, 点击其它按钮时菜单也能正常消失了.

全部评论

相关推荐

今天 11:16
湖南大学 Web前端
我看到好多人都在说0offer好焦虑,结果一看是投了百度快手字节啥的。好像大家都是只想通过校招进大厂,对小公司是不考虑的吗😂可是能进大厂的难道不是只有少部分人吗,真心发问
梦想是成为七海千秋:沉默的大多数吧,喜欢晒的都是能引起共鸣的大厂,找小厂的人,别人也不认识你这个小厂,就自己偷偷找了实际上大多数人哪有什么机会能找到大厂
点赞 评论 收藏
分享
大方的大熊猫准备进厂:1.教育背景:你希望从事什么专业的工作你的主修课就是什么;成绩优秀是你应该做的,没什么可描述的,成绩不优秀也许人家在大学忙着创业呢?(成绩优秀不一定是好事,只能说明多元化的大学你上成了高中,没有真正上明白大学,反而体现了你死板,不爱社交,没有别的突出能力) 2.实践经历:你想表达的意思没有说清楚。你是说你会个性化服务,还是你有实习经历。如果没有带来,经济收益,表彰,更好的发展前景,那你还不如说说提升了自己哪些技能。你说有人给你送锦旗我都能明白你优秀,但是你说你会xxxx,你说这话谁信,证据呢。 3.入伍经历:你描述的就是你的工作职责或者你应该做的,并没有体现出来你把这个事情做好了,而且入伍经历并不能证明你能干好你要应聘的工作,不如只写经历其余所有内容都不写。 4.荣誉技能:重点突出一下,但不要过多描述,这些荣誉的含金量懂得都懂。 重点:你要应聘什么工作(具体岗位,实习生不具体),你的期望薪资
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务