Flutter:自定义组件的上下左右弹出层

2023-11-05

背景

最近要使用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

弹出层距离目标组件的距离

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter:自定义组件的上下左右弹出层 的相关文章

随机推荐

  • xrdp无法启动

    xrdp突然无法启动l systemctl start xrdp A dependency job for xrdp service failed See journalctl xe for details 查看日志信息 journalct
  • 分享25个适合上班族的副业路子

    你是否也曾经在为每个月的工资发愁 你是否想过做点副业来增加收入 现在很多上班族的工资 已经难以满足他们的生活需求了 很多人开始尝试通过副业来增加收入 那么上班族要如何寻找适合自己的副业呢 下面就给大家分享25个适合上班族的副业赚钱路子 或许
  • 密码学加密模式分类

    目录 电子密码本模式 Electronic Codebook ECB 密码分组链接模式 Cipher Block Chaining CBC 密文反馈模式 Cipher Feedback CFB 输出反馈模式 Output Feedback
  • RepBaseRepeatMaskerEdition下载

    开源的生物信息世界居然有这么个需要注册才能下载的工具 开源世界不是怎么方便怎么来吗 这个注册真的麻烦 这里上传了一个可以使用的版本 RepBaseRepeatMaskerEdition 20170127 tar gz 想转成fasta可以用
  • 程序员财富自由之路 自媒体篇

    疫情期间 很多人的收入多多少少都受到了影响 当然 有的人坐吃山空 也有的人收入翻倍 我仔细观察了一下这些收入翻倍的人 发现他们基本都离不开三个字 哪三个字 就是自媒体 自媒体是什么 自媒体是指以内容为中心 个人创作者以网络的形式进行内容传播
  • Linux文件下I/O基础详解

    视频地址 华清远见 https www bilibili com video BV1dz411B7vj p 1 文件基础 一组相关数据的有序集合 文件类型 常规文件 r 二进制文件 ASCII码文件 目录文件 d 字符设备文件 c 块设备文
  • Python->进程-线程->TCP服务器客户端-服务端->返回固定数据的静态web服务器-学习

    作者 芝士小熊饼干 系列专栏 Python 坚持天数 12天 获取进程id和进程的父id 进程名 导入包 import multiprocessing import time import os 创建任务 def task1 print f
  • Django-admin录入中文错误:Incorrect string value

    在Django自带后台中经常会出现编码错误 Incorrect string value xE7 xAE x80 xE5 x8D x95 for column message at row 1 需要修改admin表的中的编码 才能保证数据正
  • Rust- FFI (Foreign Function Interface)

    Foreign Function Interface FFI is a mechanism that allows code written in one language to call code written in another l
  • 手写ArrayStack底层 实现代码

    一 接口的定义 package p1 接口 public interface Stack
  • 华为OD机试真题B卷 Java 实现【统计字符】,附详细解题思路

    一 题目描述 输入一行字符 分别统计出包含英文字母 空格 数字和其它字符的个数 数据范围 输入的字符串长度满足 1 le n le 1000 1 n 1000 二 输入描述 输入一行字符串 可以有空格 三 输出描述 统计其中英文字符 空格字
  • ECharts画动态仪表盘+柱状图(ajax获取+循环画图)

    出来工作几个月了 整理下最近学的的东西 刚刚才开通博客 第一篇就先画仪表盘 柱状图 有什么写的不好的地方 请大家多多指教 jsp中首先要引用几个文件 xx xxx js 是我js代码存放的文件
  • C++标准库异常类

    C 标准库异常类继承层次中的根类为exception 其定义在exception头文件中 它是C 标准库所有函数抛出异常的基类 exception的接口定义如下 namespace std class exception public ex
  • 记一个复制黏贴的功能想法(黏贴剪切板中的数字自增,复制黏贴自增)

    起因 照例是要写起因的 起因非常之简单 不知道大家有没有遇到过需要输入连续的 id 101 id 110 这类数字的时候 这个时候能做的基本上是复制100 黏贴100 然后手动改 101 102 在我的脑海里 除了使用excel 其他没有很
  • 【华为机试真题 JAVA】执行时长-100

    编程题目 100分 执行时长 2021 2022 Q2考试题 时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 本题可使用本地IDE编码 不能使用本地已
  • 面对对象--结构体和类的区别

    一 面向对象 结构体和类的区别 结构体是一种值类型 而类是引用类型 值类型用于存储数据的值 引用类型用于存储对实际数据的引用 那么结构体就是当成值来使用的 类则通过引用来对实际数据操作 构使用栈存储 Stack Allocation 而类使
  • 利用腾讯云函数隐藏C2服务器

    1 简介 腾讯云函数 可以为企业和开发者提供无服务器执行环境 无需购买和管理服务器 只需要在腾讯云上使用平台支持的语言编写核心代码并设置代码运行的条件 即可在腾讯云基础设施上弹性 安全地运行代码 C2服务器所有流量通过腾讯云函数进行转发 由
  • Html04_input框中的value/key/placeholder到底是什么

    1 input框中的value值到底是什么 value 属性为 input 元素设定值 input标签有很多类型 也就是type 对于不同的输入类型 value 属性的用法也不同 以下是一些常用type的说明 text 文本框 input默
  • 513. Find Bottom Left Tree Value

    Given a binary tree find the leftmost value in the last row of the tree Example 1 Input 2 1 3 Output 1 Example 2 Input 1
  • Flutter:自定义组件的上下左右弹出层

    背景 最近要使用Flutter实现一个下拉菜单 需求就是 在当前组件下点击 其下方弹出一个菜单选项 如下图所示 实现起来 貌似没什么障碍 在Flutter中本身就提供了弹出层PopupMenuButton组件和showMenu方法 于是开搞