Flutter:自定义组件的上下左右弹出层
背景
最近要使用Flutter实现一个下拉菜单,需求就是,在当前组件下点击,其下方弹出一个菜单选项,如下图所示:

实现起来,貌似没什么障碍,在Flutter中本身就提供了弹出层PopupMenuButton组件和showMenu方法,于是开搞,代码如下:
PopupMenuButton<String>(initialValue: '下拉菜单一',child: const Text("下拉菜单"),itemBuilder: (context) {return <PopupMenuEntry<String>>[const PopupMenuItem<String>(value: '下拉菜单一',child: Text('下拉菜单一'),),const PopupMenuItem<String>(value: '下拉菜单二',child: Text('下拉菜单二'),),const PopupMenuItem<String>(value: '下拉菜单三',child: Text('下拉菜单三'),)];},)
直接使用showMenu也行,代码如下:
showMenu(context: context,position: const RelativeRect.fromLTRB(0, 0, 0, 0),items: <PopupMenuEntry>[const PopupMenuItem(value: "下拉菜单一",child: Text("下拉菜单一"),),const PopupMenuItem(value: "下拉菜单二",child: Text("下拉菜单二"),),const PopupMenuItem(value: "下拉菜单三",child: Text("下拉菜单三"),),]);
PopupMenuButton运行看结果:

showMenu位置传的是左上角,这个就不贴图了。
看到效果后,我诧异了,这也不符合我的需求啊,直接把选项给我盖住了,这还得了,况且位置也不对啊,怎么搞?还好,无论使用PopupMenuButton还是showMenu,都给我们提供了位置。
PopupMenuButton设置位置:
offset: Offset(dx, dy)
showMenu设置位置:
position: const RelativeRect.fromLTRB(left, top, right, bottom)
使用位置后,我们再看效果:
dx设置为0,dy设置为50:
PopupMenuButton<String>(initialValue: '下拉菜单一',offset: const Offset(0, 50),itemBuilder: (context) {return <PopupMenuEntry<String>>[const PopupMenuItem<String>(value: '下拉菜单一',child: Text('下拉菜单一'),),const PopupMenuItem<String>(value: '下拉菜单二',child: Text('下拉菜单二'),),const PopupMenuItem<String>(value: '下拉菜单三',child: Text('下拉菜单三'),)];},child: Text("下拉菜单",key: _key,),)
效果如下图:

这样看起来确实好多了,但是我的疑问就来了,如果我想实现在左边展示呢?在上边、右边,甚至左上右上,左下右下呢?通过坐标计算,确实能实现,但是计算起来麻烦,也不精确,很难作为上上策,再者,这种弹窗方式样式,在实际开发中也很难满足我们的需求。
既然原生的组件无法满足我们的需求,怎么搞?只有自定义一个组件了。
今天的内容大致如下:
1、自定义弹出层效果一览
2、弹出层逻辑实现
3、使用注意事项
4、源码
一、自定义弹出层效果一览
目前自定义的组件,可以在目标组件,左、上、右、下,左上、右上,左下、右下八个方向进行精确的弹出,当然了,除此之外,也可以动态的展示到自己想要的位置,并且弹出层效果可以自定义,效果是我弹出了一个黑色矩形,你可以弹出一个列表,一个图片等等。

二、弹出层逻辑实现
1、悬浮在其他顶部小部件之上
为了更好的展示弹出效果,和不影响UI层的相关逻辑,针对弹出层,我们可以悬浮在内容层之上,做透明处理即可,这里使用到了Overlay对象,它是一个类似悬浮小弹窗,如Toast,安卓的PopupWindow效果。
相关代码如下,创建OverlayEntry,并插入到Overlay中,这样就可以把OverlayEntry中构建的小部件叠加悬浮在其他顶部小部件之上。
OverlayState overlayState = Overlay.of(key.currentContext!);OverlayEntry _overlayEntry = OverlayEntry();overlayState.insert(_overlayEntry!);
2、获取弹出目标组件的左上右下
所谓目标组件,就是,你想要在哪个组件(左上右下)进行弹出,确定了目标组件之后,为了使弹出层,精确的展示在目标组件的方位,需要拿到目标组件的位置,也就是左上右下的位置,这里使用到了GlobalKey作为获取方式,具体的位置信息获取如下:
///获取组件的位置static WidgetSize getWidgetSize(GlobalKey key) {//获取组件的位置,在左上右下final RenderBox renderBox =(key.currentContext?.findRenderObject() as RenderBox);final left = renderBox.localToGlobal(Offset.zero).dx; //左边final top = renderBox.localToGlobal(Offset(renderBox.size.width, 0)).dy;final bottom = renderBox.localToGlobal(Offset(0, renderBox.size.height)).dy;final right = renderBox.localToGlobal(Offset(renderBox.size.width, renderBox.size.height)).dx;return WidgetSize(left, top, right, bottom);}
创建记录位置对象,用来标记左上右下。
///组件对象,标记左上右下
class WidgetSize {double left;double top;double right;double bottom;WidgetSize(this.left, this.top, this.right, this.bottom);
}
3、设置弹出层的位置
弹出层位置,这里利用到了Positioned组件,控制其left和top位置,基本上和PopupMenuButton类似,无非就是自己实现了位置的测量而已。
首先根据传递的属性WindowDirection,确定要设置的方位。
具体各个方位计算如下:
目标组件下边:
top坐标:目标组件的底部坐标+边距
left坐标:目标组件的右部坐标-弹出层的宽度/2-目标组件宽度/2
目标组件左边:
top坐标:目标组件的底部坐标-弹出层的高度/2-目标组件的高度/2
left坐标:目标组件的左边坐标-弹出层的宽度-边距
目标组件上边:
top坐标:目标组件的上边坐标-弹出层的高度-边距
left坐标:目标组件的右部坐标-弹出层的宽度/2-目标组件宽度/2
目标组件右边:
top坐标:目标组件的底部坐标-弹出层的高度/2-目标组件的高度/2
left坐标:目标组件的右边坐标+边距
目标组件左上:
top坐标:目标组件的底部坐标-弹出层的高度-目标组件的高度-边距
left坐标:目标组件的左边坐标-弹出层的宽度-边距
目标组件右上:
top坐标:目标组件的底部坐标-弹出层的高度-目标组件的高度-边距
left坐标:目标组件的左边坐标+边距
目标组件左下:
top坐标:目标组件的底部坐标+边距
left坐标:目标组件的左边坐标-弹出层的宽度-边距
目标组件右下:
top坐标:目标组件+边距
left坐标:目标组件右边的坐标+边距
var size = getWidgetSize(key); //获取在目标组件的位置double widgetTop = 0.0;double widgetLeft = 0.0;switch (direction) {case WindowDirection.bottom: //下面widgetTop = size.bottom + margin;widgetLeft =size.right - childWidth / 2 - ((size.right - size.left) / 2);break;case WindowDirection.left: //左面widgetTop =size.bottom - childHeight / 2 - ((size.bottom - size.top) / 2);widgetLeft = size.left - childWidth - margin;break;case WindowDirection.top: //上面widgetTop = size.top - childHeight - margin;widgetLeft =size.right - childWidth / 2 - ((size.right - size.left) / 2);break;case WindowDirection.right: //右面widgetTop =size.bottom - childHeight / 2 - ((size.bottom - size.top) / 2);widgetLeft = size.right + margin;break;case WindowDirection.topLeft: //左上widgetTop =size.bottom - childHeight - (size.bottom - size.top) - margin;widgetLeft = size.left - childWidth - margin;break;case WindowDirection.topRight: //右上widgetTop =size.bottom - childHeight - (size.bottom - size.top) - margin;widgetLeft = size.right + margin;break;case WindowDirection.bottomLeft: //左下widgetTop = size.bottom + margin;widgetLeft = size.left - childWidth - margin;break;case WindowDirection.bottomRight: //右下widgetTop = size.bottom + margin;widgetLeft = size.right + margin;break;case WindowDirection.none: //取消 自己测量位置widgetTop = top;widgetLeft = left;break;}
三、使用注意事项
1、为了能够精确的设置弹出层的位置,其弹出层的宽度和高度是必须要传递的,也就是childWidth和childHeight属性。
2、如果想自己设置位置,可以不传childWidth和childHeight,设置direction为WindowDirection.none,并且left和top坐标需要传递。
3、margin属性设置弹出层距离目标组件的距离。
四、源码
源码地址
https://github.com/AbnerMing888/flutter_widget/blob/master/lib/utils/popup_window.dart
使用方式
PopupWindow.create(_key,const BaseWidget(width: 100,height: 100,backgroundColor: Colors.black,),direction: direction,margin: 10,childWidth: 100,childHeight: 100);
参数介绍
| 属性 | 类型 | 概述 |
| key | GlobalKey | 目标组件的key |
| child | Widget | 弹出层 |
| childWidth | double | 弹出层的宽 |
| childHeight | double | 弹出层的高 |
| direction | WindowDirection | 位置: left//左 top//上 right//右 bottom//下 topLeft, //左上角 topRight, //右上角 bottomLeft, //左下 bottomRight, //右下 none//取消位置,自己定义 |
| left | double | 相对于屏幕的左侧坐标 |
| top | double | 相对于屏幕的顶部坐标 |
| margin | double | 弹出层距离目标组件的距离 |
相关文章:
Flutter:自定义组件的上下左右弹出层
背景 最近要使用Flutter实现一个下拉菜单,需求就是,在当前组件下点击,其下方弹出一个菜单选项,如下图所示: 实现起来,貌似没什么障碍,在Flutter中本身就提供了弹出层PopupMenuButton组件和show…...
C++处理终端程序中断或意外退出的情况
目录 背景和需求解决方法关于信号类型 背景和需求 Linux环境中,有一个可执行程序,假设该程序的运行生命周期需要调用下面四个函数: int connect(); int start();int end(); int disconnect();如果用户在程序运行期间,手动CTRLC或…...
分布式锁:业务锁和定时任务锁
一:业务锁 在代码业务逻辑加锁,防止不同业务操作相同业务表导致数据错乱,设置锁进行等待。这里锁使用的是ReentrantLock。详细的介绍可以参考: https://blog.csdn.net/jerry11112/article/details/112375167 Slf4j public class…...
路由器的简单概述(详细理解+实例精讲)
系列文章目录 华为数通学习(4) 目录 系列文章目录 华为数通学习(4) 前言 一,网段间通信 二,路由器的基本特点 三,路由信息介绍 四,路由表 五,路由表的来源有哪些…...
Mapper.xml文件解析
Mapper.xml文件解析 简单解读 最近在做一个分布式项目,看到xml文件原先只是上网CV,还是要搞清楚吧! 下面是一个Mybatis的SQL映射文件的配置 <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapper PUBLIC…...
ES 7.6 - JAVA应用基础操作篇
ES 7.6 - JAVA应用基础操作篇 环境准备依赖配置 实体类准备使用说明索引/映射操作创建索引和映射索引和映射相关查询删除索引 文档操作插入数据更新数据删除数据批量操作 文档查询根据ID查询根据字段精准查询根据字段分词查询控制返回字段范围查询组合查询排序分页高亮搜索聚合…...
com.squareup.okhttp3:okhttp 组件安全漏洞及健康度分析
组件简介 维护者square组织许可证类型Apache License 2.0首次发布2016 年 1 月 2 日最新发布时间2023 年 4 月 23 日GitHub Star44403GitHub Fork9197依赖包5,582依赖存储库77,217 com.squareup.okhttp3:okhttp 一个开源的 HTTP 客户端库,可以用于 Android 和 Jav…...
【Unity的HDRP渲染管线下用Steam VR串流结合使用遇到的各种问题_SteamVR 插件和Pico串流助手】
用Steam串流VR 背景:1.项目准备:相关文档和社区资源需要下载的工具2.梳理工程渲染设置和场景烘培正确:几个概念的一些说明:1. SteamVR:2. SteamVR插件:3. OpenVR和OpenXR:4. XRI:5. Pico串流助手:6. "Mock Runtime"选项含义SteamVR插件导入配置好SteamVR Came…...
Unity——音乐、音效
在游戏运行的过程中,音效的播放时机与游戏当前内容密切相关,而且随着场景的变化、剧情的推进,背景音乐也需要适时切换,所以恰当地控制音乐和音效的播放非常重要。音乐和音效的播放、停止、切换和音量变化等,都需要由脚…...
Ubuntu 23.10 将首次推出基于 Flutter 的新 Ubuntu 商店
导读Ubuntu 正在升级其软件商店以提供顺滑的体验! 随着不断发展,Canonical 似乎全力以赴,将基于 Flutter 的元素整合到 Ubuntu 中。 在前段时间 Ubuntu 23.04 发布后,我们见到了基于 Flutter 的安装程序 ,现在&#x…...
linux scatterlist阅读三
sg_copy_buffer 函数定义: /*** sg_copy_buffer - Copy data between a linear buffer and an SG list* sgl: The SG list* nents: Number of SG entries* buf: Where to copy from* buflen: The number of bytes to copy* skip: Number of bytes to sk…...
2023新,centos7安装mysql8.0.25
2023新,centos7安装mysql8.0.25 目录 2023新,centos7安装mysql8.0.251、下载rpm文件2、安装3、配置my.cnf4、启动查看重启服务5、登入mysql并修改密码6、修改可以远程登录 1、下载rpm文件 进入到你想要的文件地址下 wget https://repo.mysql.com//mysq…...
Data Rescue Professional for Mac:专业的数据恢复工具
在数字化时代,我们的生活和工作离不开电脑和存储设备。但是,意外情况时常发生,例如误删除文件、格式化硬盘、病毒攻击等,这些都可能导致重要的数据丢失。面对数据丢失,我们迫切需要一款可靠的数据恢复工具。今天&#…...
新手小白想要做好跨境电商独立站,需要考虑哪些要素?
对于不少中小卖家而言,利用独立站出海已然成为下一个跨境热潮。但是采用独立站模式做出海生意前,卖家需要考虑哪些要素? 产品选择 对于国内的卖家来说,依托于国内强大的供应链优势,只要能把握住消费者心态࿰…...
Consul原理介绍
官方文档:https://www.consul.io/docs Raft动画演示:http://thesecretlivesofdata.com/raft/ 注册中心对比 Consul特点 服务发现、健康检查、Key/Value存储、安全服务通信(TLS证书)、多数据中心 架构 角色 数据中心 数据中心内…...
【C++实战】C++实现贪吃蛇(含源代码)—基于easyx图形库
食用指南:本文在有C基础的情况下食用更佳 🍀本文前置知识:C基础 ♈️今日夜电波:toge—あよ 0:36 ━━━━━━️💟──────── 4:03 &a…...
PHP获取两个日期之间的所有日期
下面是一个示例代码,用于计算给定开始和结束日期之间的所有日期: <?phpfunction getDatesBetween($start_date, $end_date) {// 初始化结果数组$dates array();// 将开始日期转换为时间戳$current_date strtotime($start_date);$end_date strtot…...
STL之stack(适配器讲解以及双端队列的讲解)
很多人在听到适配器的时候,应该都是懵的,因为对适配器的理解都是懵懵懂懂,其实他很好理解,就是相当于一个转换器。我们可以这样理解,就是现实当中是的插排一样,上面有三个孔的,也有两个孔的&…...
JVM解密: 解构类加载与GC垃圾回收机制
文章目录 一. JVM内存划分二. 类加载机制1. 类加载过程2. 双亲委派模型 三. GC垃圾回收机制1. 找到需要回收的内存1.1 哪些内存需要回收?1.2 基于引用计数找垃圾(Java不采取该方案)1.3 基于可达性分析找垃圾(Java采取方案) 2. 垃圾回收算法2.1 标记-清除算法2.2 标记…...
【Spring Boot】Spring Boot结合MyBatis简单实现学生信息管理模块
实战:实现学生信息管理模块 环境准备 JDKSpring BootMyBatis 创建Spring Boot项目 使用Spring Initializr创建一个新的Spring Boot项目,并添加以下依赖: Spring WebMyBatis FrameworkMySQL Driver 数据库设计 在MySQL数据库中创建一个名…...
【实战指南】SVN SSL协议不兼容问题:从TLS版本冲突到降级解决方案
1. 当SVN遇上SSL:TLS协议冲突的典型症状 最近在帮团队排查SVN代码拉取问题时,遇到了一个经典的错误提示:"error running context: an error occurred during ssl communication"。这个看似简单的报错背后,其实是现代加密…...
手把手教你用XCVU3P和FMC+接口搭建高性能PCIe载板(附原理图下载)
基于XCVU3P与FMC的高性能PCIe载板开发实战指南 在当今高速数据处理领域,FPGA因其并行计算能力和可重构特性成为关键器件。Xilinx UltraScale系列的XCVU3P芯片配合FMC扩展接口,为开发者提供了强大的硬件加速平台。本文将深入解析如何从零开始构建一个支持…...
WeChatExporter深度解析:如何三步搞定iOS微信聊天记录完整导出
WeChatExporter深度解析:如何三步搞定iOS微信聊天记录完整导出 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter 还在为无法备份微信聊天记录而烦恼吗ÿ…...
保姆级教程:用Docker快速搭建一个可复现的Hive测试环境(专治各种启动报错)
从零构建可复现的Hive沙箱:Docker Compose全流程避坑指南 每次调试Hive时遇到FAILED: HiveException或metastore连接问题,是否感觉像在破解一个没有说明书的密码锁?传统环境配置的不可复现性让问题排查变成一场噩梦。本文将带你用Docker技术…...
AutoGen多智能体框架:从协作价值到企业级实践指南
AutoGen多智能体框架:从协作价值到企业级实践指南 【免费下载链接】autogen 启用下一代大型语言模型应用 项目地址: https://gitcode.com/GitHub_Trending/au/autogen 在人工智能快速发展的今天,如何让AI系统像人类团队一样高效协作完成复杂任务&…...
OpenClaw 超级 AI 实战专栏【补充内容】AI开发实操:减少Token用量、提升模型效率的8个核心技巧(附代码)
目录 一、核心前提:理解Token消耗的关键场景 二、6种优化方案(附案例+代码) 方案1:精简Prompt(最易落地,立竿见影) 核心思路 应用案例 代码实现 方案2:上下文窗口裁剪(避免历史信息冗余) 核心思路 应用案例 代码实现 方案3:输入文本摘要压缩(批量处理场景…...
Step3-VL-10B部署案例:金融APP界面自动化测试,覆盖85%人工回归用例
Step3-VL-10B部署案例:金融APP界面自动化测试,覆盖85%人工回归用例 1. 项目背景与痛点 金融APP的每一次版本更新,都伴随着一场紧张的回归测试。测试团队需要反复验证登录、转账、理财购买、账单查询等几十个核心功能,确保新代码…...
Apache Doris 存储与查询优化实战:从架构设计到性能调优的完整指南
1. Apache Doris 架构设计精要 第一次接触Apache Doris时,我被它简洁的架构设计惊艳到了。这个MPP架构的分析型数据库,用计算存储分离的设计思路,把复杂的大数据分析变得像查普通MySQL表一样简单。FE(Frontend)和BE&am…...
Qwen3-14B-Int4-AWQ助力运维智能化:日志分析与故障排查实战
Qwen3-14B-Int4-AWQ助力运维智能化:日志分析与故障排查实战 1. 运维工程师的日常痛点 凌晨三点,你的手机突然响起。系统告警显示某核心服务出现异常,你需要立即登录服务器查看日志。面对几十GB的日志文件,你不得不用grep、awk等…...
Docker 容器中文字体及 matplotlib 环境应用
为了避开 Noto CJK 这种复杂的 TTC(TrueType Collection)大包带来的识别问题,最理想的选择是使用独立打包的 OTF 或 TTF 字体。 0. 环境检查 # 1. 更新源并安装 fontconfig apt-get update apt-get install -y fontconfig# 2. 现在 fc-cache 命令可用了,刷新系统字体 fc-…...
