flutter_portal

flutter_portal

Flutter叠加层框架 简化悬浮UI开发

flutter_portal是一个增强型Flutter叠加层库,提供声明式API创建工具提示、上下文菜单等悬浮UI。相比内置Overlay,它具有更直观的上下文、简便的对齐功能和可自定义的对齐逻辑,帮助开发者高效实现各种悬浮UI效果。该库采用声明式方法,简化了悬浮UI的创建过程,使开发者能更轻松地实现各种复杂的悬浮界面效果。

flutter_portalOverlayOverlayEntryFlutterWidgetGithub开源项目

flutter_portal:进化版的 Overlay/OverlayEntry - 声明式而非命令式,直观的上下文,以及简易的对齐

想要显示浮动覆盖层 - 工具提示、上下文菜单、对话框、气泡等?这个库是对 Flutter 内置 Overlay/OverlayEntry 的增强和替代。

🚀 优势

为什么使用 flutter_portal 而不是内置的 Overlay/OverlayEntry/OverlayPortal

  • 声明式,非命令式:与 Flutter 世界中的其他一切一样,覆盖层(portals)现在是声明式的。只需在普通的 widget 树中放置您的浮动 UI。<sub>对比:OverlayEntry 不是一个 widget,需要通过 .insert() 等方式进行命令式操作。</sub>
  • 轻松实现对齐:内置支持将覆盖层与 UI 组件对齐。<sub>对比:用几行代码就能从头开始创建自定义上下文菜单;而 Overlay 使得将工具提示/菜单与 widget 对齐变得不那么简单。</sub>
  • 可自定义的对齐逻辑:例如,确保 portal 目标永远不会在屏幕外渲染(shiftToWithinBound),将其与 portal 而不是父 widget 对齐(alignToPortal),您甚至可以创建自己的对齐算法(扩展 EnhancedCompositedTransformAnchor)。<sub>对比:Overlay 似乎没有这种功能。</sub>
  • 直观的 Context:覆盖层条目使用其直观的父级作为 context 进行构建。<sub>对比:Overlay 方法使用远处的覆盖层作为其 context。更新:OverlayPortal(受本包启发)在这方面有所改进。</sub>

因此,还具有以下优点:

  • 轻松实现可恢复属性:由于显示覆盖层就像执行 setState 一样简单,RestorableProperty 可以很好地工作。<sub>对比:使用 Overlay 方法时,当应用程序被操作系统终止时,我们的模态框状态不会被恢复。</sub>
  • 正确的 Theme/provider:由于覆盖层条目具有直观的 context,它可以访问与显示覆盖层的 widget 相同的 Theme 和不同的 provider<sub>对比:Overlay 方法会产生令人困惑的 Theme 和 provider。更新:OverlayPortal(受本包启发)在这方面有所改进。</sub>

👀 展示代码

PortalTarget( // 1. 声明式:只需将 `portalFollower` 作为普通 widget 提供 // 2. 内部具有直观的 BuildContext portalFollower: MyAwesomeOverlayWidget(), // 3. 可以随意将"follower"相对于"child"对齐 anchor: Aligned.center, child: MyChildWidget(), )

要从 0.x 迁移到 1.x,请参阅 readme 的最后一节。

🪜 示例

查看 examples 文件夹以了解如何使用 flutter_portal 的示例:

部分截图:

上下文菜单引导视图
<img width="300px" src="https://yellow-cdn.veclightyear.com/835a84d5/01194457-be4f-4ee2-aa68-a47fd0dc6cad.png"><img src="https://yellow-cdn.veclightyear.com/835a84d5/49c56226-c731-4092-b81f-19f5d48d85af.gif" alt="Discovery example" style="300px">

🧭 使用方法

  1. 安装。按照安装此包的标准步骤进行。最简单的方法可能是 flutter pub add flutter_portal
  2. 添加 Portal widget。例如,将其放在 MaterialApp 之上。每个应用程序只需要一个 Portal
  3. 在需要显示覆盖层的地方使用 PortalTarget

📚 教程:显示上下文菜单

在这个例子中,我们将看到如何使用 flutter_portal 在点击 RaisedButton 后显示菜单。

添加 Portal widget

在做任何事情之前,您必须在 widget 树中插入 Portal widget。follower widget 将表现得好像它们是作为这个 widget 的子项插入的。

您可以将这个 Portal 放在 MaterialApp 之上或靠近路由的根部:

Portal( child: MaterialApp(...) )

按钮

首先,我们需要创建一个渲染 RaisedButtonStatefulWidget

class MenuExample extends StatefulWidget { _MenuExampleState createState() => _MenuExampleState(); } class _MenuExampleState extends State<MenuExample> { Widget build(BuildContext context) { return Scaffold( body: Center( child: RaisedButton( onPressed: () {}, child: Text('show menu'), ), ), ); } }
<p align="center"> <img src="https://yellow-cdn.veclightyear.com/835a84d5/3519d458-852b-4660-887b-ce6168abb246.png" alt="image" width="200px"> </p>

菜单 - 初始迭代

然后,我们需要在 widget 树中插入 PortalTarget

我们希望上下文菜单紧靠 RaisedButton 渲染。 因此,我们的 PortalTarget 应该是 RaisedButton 的父级,如下所示:

child: PortalTarget( visible: // TODO anchor: // TODO portalFollower: // TODO child: RaisedButton(...), ),

我们可以将菜单传递给 PortalTarget

PortalTarget( visible: true, anchor: Filled(), portalFollower: Material( elevation: 8, child: IntrinsicWidth( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile(title: Text('option 1')), ListTile(title: Text('option 2')), ], ), ), ), child: RaisedButton(...), )
<p align="center"> <img width="200px" src="https://yellow-cdn.veclightyear.com/835a84d5/8c1938bb-bb97-4dca-a192-30a49822b198.png"> </p>

在这个阶段,您可能会注意到两件事:

  • 我们的菜单是全屏的(因为 anchorFilled
  • 我们的菜单始终可见(因为 visibletrue

更改对齐方式

让我们先解决全屏问题,并更改我们的代码,使菜单在 RaisedButton 的右侧渲染。

要将菜单对齐到按钮周围,我们可以更改 anchor 参数:

PortalTarget( visible: true, anchor: const Aligned( follower: Alignment.topLeft, target: Alignment.topRight, ), portalFollower: Material(...), child: RaisedButton(...), )
<p align="center"> <img width="200px" src="https://yellow-cdn.veclightyear.com/835a84d5/09125d1b-b7b3-4291-a818-6778db9088dc.png"> </p> 这段代码的意思是,将菜单的左上角与 `RaisedButton` 的右上角对齐。这样,我们的菜单就不再是全屏的,而是位于按钮的右侧。

显示菜单

最后,我们可以更新代码,使菜单只在点击按钮时显示。

为此,我们需要在 StatefulWidget 中声明一个新的布尔值,表示菜单是否打开:

class _MenuExampleState extends State<MenuExample> { bool isMenuOpen = false; ... }

然后,我们将这个 isMenuOpen 变量传递给 PortalEntry

PortalTarget( visible: isMenuOpen, ... )

接着,在 RaisedButtononPressed 回调中,我们可以更新这个 isMenuOpen 变量:

RaisedButton( onPressed: () { setState(() { isMenuOpen = true; }); }, child: Text('show menu'), ),

隐藏菜单

最后一步是在用户点击菜单外部时关闭菜单。

这可以通过结合使用第二个 PortalEntry 和 [GestureDetector] 来实现,如下所示:

PortalTarget( visible: isMenuOpen, portalFollower: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { setState(() { isMenuOpen = false; }); }, ), ... ),

🎼 概念

在使用flutter_portal时,有几个概念需要充分理解。特别是如果你想支持自定义用例,这在提供的抽象API中是很容易实现的。

以下将高层次地解释你需要理解的每个抽象概念。你会在类名(如Portal部件或PortalTarget部件)以及参数名中找到它们。

Portal(传送门)

Portal(或者如果你只有一个的话,就是the portal)是用于进行所有portal工作的空间。在底层,这意味着你有一个部件,允许其子树放置相互连接的目标和跟随者。

Portal还定义了可供任何跟随者在屏幕上渲染的可用区域(矩形边界)。

具体来说,你可能会将整个MaterialApp包裹在一个单独的Portal部件中,这意味着你可以使用应用的整个区域来渲染附加到Portal部件子级的目标的跟随者。

Target(目标)

目标是portal内可以被跟随者跟随的任何位置。这允许你将任何你想要叠加的内容附加到UI中的特定位置,无论它如何动态移动。

在底层,这意味着你将UI中你想要跟随的部分包裹在一个PortalTarget部件中并进行配置。

示例

想象你想在应用中当头像被悬停时显示工具提示。在这种情况下,头像将是portal的目标,可用于锚定叠加的工具提示。

另一个例子是下拉菜单。显示当前选择的部件是目标,当点击它时,下拉选项将通过portal作为跟随者叠加显示。

Follower(跟随者)

跟随者只能与目标结合使用。你可以将其用于任何你想要叠加在UI顶部的内容,附加到目标上。

具体来说,这意味着你可以为每个PortalTarget传递一个follower,当你指定时,它将显示在UI上方的portal内。

示例

如果你想使用flutter_portal显示一个自动完成文本字段,你会想要跟随文本字段来叠加你的自动完成建议。在这种情况下,自动完成建议的部件将是portal的跟随者

Anchor(锚点)

锚点定义了目标和跟随者之间的布局连接。通常,锚点被实现为一个抽象API,提供支持任何你想要的定位所需的所有信息。这意味着锚点可以基于相关portal、目标和跟随者的属性来定义。

默认实现了一些锚点,例如AlignedFilled

⛵ 从0.x版本迁移

从0.x到1.0版本有一些破坏性变更(主要由#44引入),但可以轻松迁移。以下是示例:

PortalEntry( portalAnchor: Alignment.topLeft, childAnchor: Alignment.topRight, portal: MyAwesomePortalWidget(), child: MyAwesomeChildWidget(), )

变为:

PortalTarget( anchor: const Aligned( follower: Alignment.topLeft, target: Alignment.topRight, ), portalFollower: MyAwesomePortalWidget(), child: MyAwesomeChildWidget(), )

如果你原本使用PortalEntry时没有设置portalAnchor/childAnchor(即使其全屏显示),那么你可以这样写:

PortalTarget( anchor: const Filled(), ... )

✨ 致谢

所有者

  • @rrousselGit:这个包的前任所有者。于2019年12月创建这个包,并主要维护到2022年初。贡献包括:实现包的功能,包括代码、文档、示例等。更改渲染算法。移除PortalEntry的泛型。允许延迟PortalEntry的消失,对离开动画很有用。
  • @fzyzcjy:这个包的现任所有者。详见CHANGELOG.md了解贡献。

贡献者

编辑推荐精选

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

iTerms

iTerms

企业专属的AI法律顾问

iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。

SimilarWeb流量提升

SimilarWeb流量提升

稳定高效的流量提升解决方案,助力品牌曝光

稳定高效的流量提升解决方案,助力品牌曝光

Sora2视频免费生成

Sora2视频免费生成

最新版Sora2模型免费使用,一键生成无水印视频

最新版Sora2模型免费使用,一键生成无水印视频

下拉加载更多