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数据库中创建一个名…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
