Flutter Overlay 你用上了么

Flutter Overlay 你用上了么

<img src="https://ducafecat.oss-cn-beijing.aliyuncs.com/podcast/2023/05/019d70aac9f69a63abd4fb93d169de05.jpeg" alt="Flutter overlay did you use it?" style="width:90%;" />

前言

Flutter中的Overlay是一个用于在屏幕上显示浮层的组件。它可以用来在应用程序中创建弹出窗口、提示框、菜单、对话框等等。

Overlay通常用于在用户与应用程序交互时显示临时性的UI元素,例如:用户点击按钮时显示下拉菜单、用户长按屏幕时显示上下文菜单、显示警告或错误消息等等。

Overlay通常包含一个Stack布局,每个浮层都是一个Positioned widget,可以添加到Stack中。这样,可以将多个浮层叠加在一起,并控制它们的层次关系。

Flutter中的Overlay可以让开发者轻松创建复杂的UI,同时还可以保持应用程序的性能和响应度。

本文中提供的代码示例演示了如何在Flutter中使用Overlay制作工作覆盖层。Overlay包括两个基本组件:OverlayState和OverlayEntry。OverlayState管理所有OverlayEntry,OverlayEntry定义覆盖层中的内容。在示例中,OverlayEntry包含一个带有文本和颜色的容器,可以在屏幕上显示。这些条目可以通过OverlayState的insert和remove方法添加和删除。

原文 https://ducafecat.com/blog/flutter-overlay-did-you-use-it

<img src="https://ducafecat.oss-cn-beijing.aliyuncs.com/podcast/2023/05/15894a6d156e574b80aa96dc62a13740.png" alt="image-20230518102503338" style="width:50%;" />

视频

https://www.bilibili.com/video/BV1qX4y1C7rb/

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_overlay

参考

https://api.flutter.dev/flutter/widgets/Overlay-class.html

https://api.flutter.dev/flutter/widgets/OverlayEntry-class.html

OverlayEntry

Overlay 通过将独立的子窗口小部件插入到重叠的堆栈中,使它们在其他窗口小部件之上“浮动”可视元素。覆盖允许每个小部件使用 OverlayEntry 对象管理它们在覆盖中的参与。

构造函数

OverlayEntry({
  required this.builder,
  bool opaque = false,
  bool maintainState = false,
})
  • builder:此属性用于此条目,并将在条目位置的覆盖中包含此构建器构建的小部件。
  • opaque:此属性用于获取bool值,该值决定此条目是否阻止整个覆盖。如果条目声明为不透明,则为了提高效率,除非它们具有maintainState设置,否则覆盖将跳过在该条目下面构建条目。
  • maintainState:如果您需要在OverlayEntry中使用有状态的小部件,那么您需要将maintainState设置为true,以便小部件可以保持其状态并接收生命周期方法。如果您只是在OverlayEntry中显示一个静态小部件,则可以将maintainState设置为false,以便小部件可以释放内存和资源,不会对性能产生过多影响。

OverlayEntry方法

只有一个OverlayEntry的方法

  • remove:此方法用于从覆盖层中删除此条目

OverlayState

Overlay的当前状态用于将OverlayEntries插入覆盖层中。

OverlayState方法

  • debugIsVisible:此方法用于检查给定的OverlayEntry是否可见,并返回布尔值。
  • insert:此方法用于将给定的OverlayEntry插入覆盖层中。
  • insertAll:此方法用于获取OverlayEntries的List,并将所有条目插入覆盖层中。您还可以指定上述和下述属性以说明要插入条目的顺序。
  • rearrange:它可以重新排列当前在屏幕上叠加的所有小部件。具体来说,rearrange方法会将最后添加的OverlayEntry小部件移动到最上面,并将其他小部件移动到下面。这可以用于确保最新的OverlayEntry始终位于最上面,并且可以接收用户的触摸事件等。

步骤

第一步:成员变量

  /// overlay 状态
  OverlayState? overlayState;

  /// overlay 层集合
  List<OverlayEntry> entriesList = [];
  @override
  void initState() {
    super.initState();
    // 获取 overlay 状态
    overlayState = Overlay.of(context);
  }
  @override
  void dispose() {
    // 销毁
    overlayState?.dispose();
    super.dispose();
  }

第二步:随机层显示

  // 随机位置显示层
  void showRandomOverlay(BuildContext context) {
    // 随机颜色
    final bgColor = Color.fromARGB(
      255,
      1 + Random().nextInt(254),
      1 + Random().nextInt(254),
      1 + Random().nextInt(254),
    );

    // 屏幕宽度
    final screenWidth = MediaQuery.of(context).size.width;

    // 随机屏幕高度
    final randomHeight =
        MediaQuery.of(context).size.height * Random().nextDouble();

    OverlayEntry? overlayEntry;
    overlayEntry = OverlayEntry(builder: (context) {
      return Positioned(
        // 指定位置
        left: 0,
        top: randomHeight,
        child: GestureDetector(
          // 点击删除
          onTap: () {
            overlayEntry?.remove();
            entriesList.remove(overlayEntry);
          },
          // 背景随机色
          child: Container(
            width: screenWidth,
            height: 100,
            color: bgColor,
            child: Center(
              // 提示文字
              child: Text(
                "这是一个 overlay ${Random().nextInt(100)} 层, 点击关闭",
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  decoration: TextDecoration.none,
                ),
              ),
            ),
          ),
        ),
      );
    });
    overlayState?.insert(overlayEntry);
    entriesList.add(overlayEntry);
  }

第三步:控制按钮

  // 控制按钮
  Widget _buildBtns() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        // 随机新增
        ElevatedButton(
          onPressed: () => showRandomOverlay(context),
          child: const Text("随机新增"),
        ),

        // 关闭所有
        ElevatedButton(
          onPressed: () {
            for (final entry in entriesList) {
              entry.remove();
            }
            entriesList = [];
          },
          child: const Text("关闭所有"),
        ),

        // 随机排序
        ElevatedButton(
          onPressed: () {
            // 从屏幕上移除
            for (final entry in entriesList) {
              entry.remove();
            }

            // 使用Random类创建随机数生成器
            Random random = Random();

            // 使用List的sublist()方法创建一个新列表
            List<OverlayEntry> shuffledEntries = entriesList.sublist(0);

            // 调用List的shuffle()方法,传入一个随机数生成器
            shuffledEntries.shuffle(random);

            // 插入界面
            overlayState?.insertAll(shuffledEntries);
          },
          child: const Text("随机排序"),
        ),
      ],
    );
  }

最后:主视图显示

  // 主视图
  Widget _mainView() {
    return Padding(
      padding: const EdgeInsets.only(top: 100),
      child: Align(
        alignment: Alignment.topCenter,
        child: _buildBtns(),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _mainView(),
    );
  }

完整代码

lib/overlay_view.dart

import 'dart:math';

import 'package:flutter/material.dart';

class OverlayUsePage extends StatefulWidget {
  const OverlayUsePage({super.key});

  @override
  State<OverlayUsePage> createState() => _OverlayUsePageState();
}

class _OverlayUsePageState extends State<OverlayUsePage> {
  /// overlay 状态
  OverlayState? overlayState;

  /// overlay 层集合
  List<OverlayEntry> entriesList = [];

  // 随机位置显示层
  void showRandomOverlay(BuildContext context) {
    // 随机颜色
    final bgColor = Color.fromARGB(
      255,
      1 + Random().nextInt(254),
      1 + Random().nextInt(254),
      1 + Random().nextInt(254),
    );

    // 屏幕宽度
    final screenWidth = MediaQuery.of(context).size.width;

    // 随机屏幕高度
    final randomHeight =
        MediaQuery.of(context).size.height * Random().nextDouble();

    OverlayEntry? overlayEntry;
    overlayEntry = OverlayEntry(builder: (context) {
      return Positioned(
        // 指定位置
        left: 0,
        top: randomHeight,
        child: GestureDetector(
          // 点击删除
          onTap: () {
            overlayEntry?.remove();
            entriesList.remove(overlayEntry);
          },
          // 背景随机色
          child: Container(
            width: screenWidth,
            height: 100,
            color: bgColor,
            child: Center(
              // 提示文字
              child: Text(
                "这是一个 overlay ${Random().nextInt(100)} 层, 点击关闭",
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  decoration: TextDecoration.none,
                ),
              ),
            ),
          ),
        ),
      );
    });
    overlayState?.insert(overlayEntry);
    entriesList.add(overlayEntry);
  }

  // 控制按钮
  Widget _buildBtns() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        // 随机新增
        ElevatedButton(
          onPressed: () => showRandomOverlay(context),
          child: const Text("随机新增"),
        ),

        // 关闭所有
        ElevatedButton(
          onPressed: () {
            for (final entry in entriesList) {
              entry.remove();
            }
            entriesList = [];
          },
          child: const Text("关闭所有"),
        ),

        // 随机排序
        ElevatedButton(
          onPressed: () {
            // 从屏幕上移除
            for (final entry in entriesList) {
              entry.remove();
            }

            // 使用Random类创建随机数生成器
            Random random = Random();

            // 使用List的sublist()方法创建一个新列表
            List<OverlayEntry> shuffledEntries = entriesList.sublist(0);

            // 调用List的shuffle()方法,传入一个随机数生成器
            shuffledEntries.shuffle(random);

            // 插入界面
            overlayState?.insertAll(shuffledEntries);
          },
          child: const Text("随机排序"),
        ),
      ],
    );
  }

  // 主视图
  Widget _mainView() {
    return Padding(
      padding: const EdgeInsets.only(top: 100),
      child: Align(
        alignment: Alignment.topCenter,
        child: _buildBtns(),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    // 获取 overlay 状态
    overlayState = Overlay.of(context);
  }

  @override
  void dispose() {
    // 销毁
    overlayState?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _mainView(),
    );
  }
}

小结

本文提供了一个很好的介绍Flutter Overlay的概念和使用方法。无论是初学者还是有经验的开发人员,都可以从本文中获得新的知识和见解。如果你想在你的Flutter项目中实现工作覆盖层,本文是一个很好的起点。


© 猫哥
ducafecat.com

end

本文由mdnice多平台发布

全栈开发者 最近专注于 Flutter/Golang/Nodejs/K8s/CICD/基于Docker前端基础建设 , 喜欢技术分享

643 声望
113 粉丝
0 条评论
推荐阅读
学会这些 Web API 使你的开发效率翻倍
随着浏览器的日益壮大,浏览器自带的功能也随着增多,在 Web 开发过程中,我们经常会使用一些 Web API 增加我们的开发效率。本篇文章主要选取了一些有趣且有用的 Web API 进行介绍,并且 API 可以在线运行预览。C...

九旬13阅读 1.6k

封面图
终于卷完了!Redis 打怪升级进阶成神之路(2023 最新版)!
是一种非关系型数据库服务,它能解决常规数据库的并发能力,比如传统的数据库的IO与性能的瓶颈,同样它是关系型数据库的一个补充,有着比较好的高效率与高性能。专注于key-value查询的redis、memcached、ttserver。

民工哥12阅读 1.1k

封面图
花了几个月时间把 MySQL 重新巩固了一遍,梳理了一篇几万字 “超硬核” 的保姆式学习教程!(持续更新中~)
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。

民工哥14阅读 2k

封面图
疫情已过,2023 我的前端面试记录
顺利入职。把我最近找工作的心得记录下来。工作交接确定 lastday整理手头工作,相关对接人、交接人放文档中工作交接过渡阶段。做好被咨询者,该拉人拉人,该拉群拉群平时沟通顺畅的同事如果没有 WX 可以加一个属...

linong11阅读 1.1k

思否 CTO 祁宁:社区问答是激荡高级智慧的头脑风暴
在祁宁家里,有一套完整的赛车模拟器,他甚至还请人到国外代购了最新的 VR 设备。作为沉浸式赛车游戏发烧友,除了享受速度与激情带来的愉悦感,祁宁在玩的过程中更多的是思考如何将技术能力进行产品化的问题。

万事ONES6阅读 13k评论 1

封面图
硬卷完了!MongoDB 打怪升级进阶成神之路( 2023 最新版 )!
前面我们学习:MySQL 打怪升级进阶成神之路、Redis 打怪升级进阶成神之路,然后我们还在继续 NoSQL 的卷王之路。从第一篇文章开始,我们逐步详细介绍了 MogoDB 基础概念、安装和最基本的CURD操作、索引和聚合、工...

民工哥6阅读 614

封面图
「刷起来」Go必看的进阶面试题详解
逃逸分析是Go语言中的一项重要优化技术,可以帮助程序减少内存分配和垃圾回收的开销,从而提高程序的性能。下面是一道涉及逃逸分析的面试题及其详解。

王中阳Go4阅读 1.9k评论 1

封面图

全栈开发者 最近专注于 Flutter/Golang/Nodejs/K8s/CICD/基于Docker前端基础建设 , 喜欢技术分享

643 声望
113 粉丝
宣传栏