当前位置: 首页 > news >正文

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实现一个下拉菜单&#xff0c;需求就是&#xff0c;在当前组件下点击&#xff0c;其下方弹出一个菜单选项&#xff0c;如下图所示&#xff1a; 实现起来&#xff0c;貌似没什么障碍&#xff0c;在Flutter中本身就提供了弹出层PopupMenuButton组件和show…...

C++处理终端程序中断或意外退出的情况

目录 背景和需求解决方法关于信号类型 背景和需求 Linux环境中&#xff0c;有一个可执行程序&#xff0c;假设该程序的运行生命周期需要调用下面四个函数&#xff1a; int connect(); int start();int end(); int disconnect();如果用户在程序运行期间&#xff0c;手动CTRLC或…...

分布式锁:业务锁和定时任务锁

一&#xff1a;业务锁 在代码业务逻辑加锁&#xff0c;防止不同业务操作相同业务表导致数据错乱&#xff0c;设置锁进行等待。这里锁使用的是ReentrantLock。详细的介绍可以参考&#xff1a; https://blog.csdn.net/jerry11112/article/details/112375167 Slf4j public class…...

路由器的简单概述(详细理解+实例精讲)

系列文章目录 华为数通学习&#xff08;4&#xff09; 目录 系列文章目录 华为数通学习&#xff08;4&#xff09; 前言 一&#xff0c;网段间通信 二&#xff0c;路由器的基本特点 三&#xff0c;路由信息介绍 四&#xff0c;路由表 五&#xff0c;路由表的来源有哪些…...

Mapper.xml文件解析

Mapper.xml文件解析 简单解读 最近在做一个分布式项目&#xff0c;看到xml文件原先只是上网CV&#xff0c;还是要搞清楚吧&#xff01; 下面是一个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 客户端库&#xff0c;可以用于 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——音乐、音效

在游戏运行的过程中&#xff0c;音效的播放时机与游戏当前内容密切相关&#xff0c;而且随着场景的变化、剧情的推进&#xff0c;背景音乐也需要适时切换&#xff0c;所以恰当地控制音乐和音效的播放非常重要。音乐和音效的播放、停止、切换和音量变化等&#xff0c;都需要由脚…...

Ubuntu 23.10 将首次推出基于 Flutter 的新 Ubuntu 商店

导读Ubuntu 正在升级其软件商店以提供顺滑的体验&#xff01; 随着不断发展&#xff0c;Canonical 似乎全力以赴&#xff0c;将基于 Flutter 的元素整合到 Ubuntu 中。 在前段时间 Ubuntu 23.04 发布后&#xff0c;我们见到了基于 Flutter 的安装程序 &#xff0c;现在&#x…...

linux scatterlist阅读三

sg_copy_buffer 函数定义&#xff1a; /*** 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新&#xff0c;centos7安装mysql8.0.25 目录 2023新&#xff0c;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:专业的数据恢复工具

在数字化时代&#xff0c;我们的生活和工作离不开电脑和存储设备。但是&#xff0c;意外情况时常发生&#xff0c;例如误删除文件、格式化硬盘、病毒攻击等&#xff0c;这些都可能导致重要的数据丢失。面对数据丢失&#xff0c;我们迫切需要一款可靠的数据恢复工具。今天&#…...

新手小白想要做好跨境电商独立站,需要考虑哪些要素?

对于不少中小卖家而言&#xff0c;利用独立站出海已然成为下一个跨境热潮。但是采用独立站模式做出海生意前&#xff0c;卖家需要考虑哪些要素&#xff1f; 产品选择 对于国内的卖家来说&#xff0c;依托于国内强大的供应链优势&#xff0c;只要能把握住消费者心态&#xff0…...

Consul原理介绍

官方文档&#xff1a;https://www.consul.io/docs Raft动画演示&#xff1a;http://thesecretlivesofdata.com/raft/ 注册中心对比 Consul特点 服务发现、健康检查、Key/Value存储、安全服务通信&#xff08;TLS证书&#xff09;、多数据中心 架构 角色 数据中心 数据中心内…...

【C++实战】C++实现贪吃蛇(含源代码)—基于easyx图形库

食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f340;本文前置知识&#xff1a;C基础 ♈️今日夜电波&#xff1a;toge—あよ 0:36 ━━━━━━️&#x1f49f;──────── 4:03 &a…...

PHP获取两个日期之间的所有日期

下面是一个示例代码&#xff0c;用于计算给定开始和结束日期之间的所有日期&#xff1a; <?phpfunction getDatesBetween($start_date, $end_date) {// 初始化结果数组$dates array();// 将开始日期转换为时间戳$current_date strtotime($start_date);$end_date strtot…...

STL之stack(适配器讲解以及双端队列的讲解)

很多人在听到适配器的时候&#xff0c;应该都是懵的&#xff0c;因为对适配器的理解都是懵懵懂懂&#xff0c;其实他很好理解&#xff0c;就是相当于一个转换器。我们可以这样理解&#xff0c;就是现实当中是的插排一样&#xff0c;上面有三个孔的&#xff0c;也有两个孔的&…...

JVM解密: 解构类加载与GC垃圾回收机制

文章目录 一. JVM内存划分二. 类加载机制1. 类加载过程2. 双亲委派模型 三. GC垃圾回收机制1. 找到需要回收的内存1.1 哪些内存需要回收&#xff1f;1.2 基于引用计数找垃圾(Java不采取该方案)1.3 基于可达性分析找垃圾(Java采取方案) 2. 垃圾回收算法2.1 标记-清除算法2.2 标记…...

【Spring Boot】Spring Boot结合MyBatis简单实现学生信息管理模块

实战&#xff1a;实现学生信息管理模块 环境准备 JDKSpring BootMyBatis 创建Spring Boot项目 使用Spring Initializr创建一个新的Spring Boot项目&#xff0c;并添加以下依赖&#xff1a; Spring WebMyBatis FrameworkMySQL Driver 数据库设计 在MySQL数据库中创建一个名…...

【Java List与Map】List<T> Map与Map List<T>的区别(126)

List&#xff1c;T&#xff1e; Map&#xff1a;List里面的数据类型包含Map&#xff1b; Map List&#xff1c;T&#xff1e;&#xff1a;Map里面value的数据类型包含List&#xff1b; 测试案例&#xff1a; import java.util.ArrayList; import java.util.HashMap; import j…...

【FreeRTOS】常用函数总结

xTaskCreate()&#xff1a; 用法&#xff1a; xTaskCreate(taskFunction, taskName, stackSize, parameters, priority, taskHandle)参数&#xff1a; taskFunction&#xff1a;任务函数&#xff0c;即任务的入口函数。taskName&#xff1a;任务的名称。stackSize&#xff1a;任…...

The Cherno——OpenGL

The Cherno——OpenGL 1. 欢迎来到OpenGL OpenGL是一种跨平台的图形接口&#xff08;API&#xff09;&#xff0c;就是一大堆我们能够调用的函数去做一些与图像相关的事情。特殊的是&#xff0c;OpenGL允许我们访问GPU&#xff08;Graphics Processing Unit 图像处理单元&…...

linux中学习控制进程的要点

1. 进程创建 1.1 fork函数 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;自进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 进程调用fork&#xff0c;当控制转移到内核中的fork代码后&#xff0c;内核会做以下操作 分配新的内存块和…...

C++Qt QSS要注意的坑

qss源自css&#xff0c;相当于css的一个子集&#xff0c;主要支持的是css2标准&#xff0c;很多网上的css3的标准的写法在qss这里是不生效的&#xff0c;所以不要大惊小怪。 qss也不是完全支持所有的css2&#xff0c;比如text-align官方文档就有说明&#xff0c;只支持 QPushB…...

LeetCode每日一题:56. 合并区间(2023.8.27 C++)

目录 56. 合并区间 题目描述&#xff1a; 实现代码与解析&#xff1a; 排序 贪心 原理思路&#xff1a; 56. 合并区间 题目描述&#xff1a; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&…...

电视盒子什么牌子好?经销商整理线下热销电视盒子品牌排行榜

在面对众多品牌和型号时&#xff0c;不知道电视盒子哪个牌子好的消费者超多&#xff0c;很多人进店都会问我电视盒子哪款好&#xff1f;我根据店铺内近两年的销量情况整理了电视盒子品牌排行榜&#xff0c;看看实体店哪些电视盒子最值得入手吧。 TOP 1.泰捷WEBOX 40Pro Max电视…...

JavaScript关于函数的小挑战

题目 回到两个体操队&#xff0c;即海豚队和考拉队! 有一个新的体操项目&#xff0c;它的工作方式不同。 每队比赛3次&#xff0c;然后计算3次得分的平均值&#xff08;所以每队有一个平均分&#xff09;。 只有当一个团队的平均分至少是另一个团队的两倍时才会获胜。否则&…...

机器学习深度学习——针对序列级和词元级应用微调BERT

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——NLP实战&#xff08;自然语言推断——注意力机制实现&#xff09; &#x1f4da;订阅专栏&#xff1a;机…...

重启Mysql时报错rm: cannot remove ‘/var/lock/subsys/mysql‘: Permission denied

只有用mysql重启时报错&#xff0c;用root不报错 [mysqlt3-dtpoc-dtpoc-web04 bin]$ service mysql restart Shutting down MySQL.. SUCCESS! rm: cannot remove /var/lock/subsys/mysql: Permission denied Starting MySQL.. SUCCESS! [roott3-dtpoc-dtpoc-web04 ~]# serv…...