Project Icon

flutter_portal

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

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

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

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

🚀 优势

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

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

因此,还具有以下优点:

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

👀 展示代码

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

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

🪜 示例

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

部分截图:

上下文菜单引导视图
Discovery example

🧭 使用方法

  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 {
  @override
  _MenuExampleState createState() => _MenuExampleState();
}

class _MenuExampleState extends State<MenuExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton(
          onPressed: () {},
          child: Text('show menu'),
        ),
      ),
    );
  }
}

image

菜单 - 初始迭代

然后,我们需要在 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(...),
)

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

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

更改对齐方式

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

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

PortalTarget(
  visible: true,
  anchor: const Aligned(
    follower: Alignment.topLeft,
    target: Alignment.topRight,
  ),
  portalFollower: Material(...),
  child: RaisedButton(...),
)

这段代码的意思是,将菜单的左上角与 `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了解贡献。

贡献者

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

稿定AI

稿定设计 是一个多功能的在线设计和创意平台,提供广泛的设计工具和资源,以满足不同用户的需求。从专业的图形设计师到普通用户,无论是进行图片处理、智能抠图、H5页面制作还是视频剪辑,稿定设计都能提供简单、高效的解决方案。该平台以其用户友好的界面和强大的功能集合,帮助用户轻松实现创意设计。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号