Flutter TolyUI 框架#02 | Popover 与 Tooltip 设计

4,042 阅读10分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!


《Flutter TolyUI 框架》系列前言:

TolyUI张风捷特烈 打造的 Fluter 全平台应用开发 UI 框架。具备 全平台组件化源码开放响应式 四大特点。可以帮助开发者迅速构建具有响应式全平台应用软件:

开源地址: github.com/TolyFx/toly…

image.png

该系列将详细介绍 TolyUI 框架使用方式、框架开发过程中的技术知识、设计理念、难题解决等。本篇将介绍 TolyUI 反馈模块 中的两个重要组件,它们的视图职能如下视频所示:


一、Tooltip 的再设计

TolyUI 框架中组件的设计原则之一是:

不轻易增加组件,每个 TolyUI 中被设计的组件,都应有它的设计动机。

组件的设计动机包括:

  • [1]. 提供 Flutter 中不存在的常用交互组件。
  • [2]. 对于 Flutter 已有但支持比较弱的组件, TolyUI 将基于源码,进行改造,以此拓展功能。

1.Tooltip 设计动机

Flutter 虽然内置了 Tooltip 组件,但它只能进行简单展示提示信息,效果如下:

03.gif

这种视觉效果并不能满足通用场景:比如提示框的对齐方式,或者气泡包裹自适应边界灵活自定义内容 等。这就是Tooltip 在 TolyUI 中再设计动机,如下通过 TolyTooltip 组件达到的效果:

无标题项目.gif


2. tolyui_feedback 模块

TolyUI 的模块化,将相对独立的功能 单独分包。功能上 Tooltip 在一个组件基础上,展开提示信息。属于一种交互的反馈,反馈内容是静态信息,不参与交互。反馈相关的组件将分离 【tolyui_feedback】 包提供功能:

image.png

同样,使用者可以 tolyui_feedback 独立模块包,或者使用 tolyui 全家桶来享用 TolyTooltip 组件。

# 仅使用反馈模块 
dependencies: 
   tolyui_feedback: ^last_version

# 使用 tolyui 全家桶 
dependencies: 
    tolyui: ^last_version

3. TolyTooltip 的功能

Tooltip在的设计语义是: 具有辅助反馈的功能 提示浮层。它会在目标组件 child 为基础,弹出用于展示的文字浮窗。这种浮窗是非侵扰性,一般不会响应事件,也不会消费目标组件的点击事件。在鼠标悬浮/手势长按事件中动画展开浮层。

有道飞书
image.pngimage.png
  • [1]. 动画展示/隐藏浮层弹框。
  • [2]. 支持 12 种弹框与目标组件的对齐方式。
  • [3]. 支持气泡框和非气泡框,填充与边模式线的弹框。
  • [4]. 支持边界溢出检测,并自动适应偏移功能。

二 、TolyTooltip 用法

对于桌面端和 web 平台来说,悬浮展示提示信息是一个非常常用的功能。下面介绍一下 TolyTooltip 的用法,感受一下它所带来的便利性和强大功能。

image.png

TolyTooltip 的使用案例介绍可以网站访问 TolyUI 的 web 版 Flutter 应用。

组件/反馈组件/popover: toly1994.com/ui/#/widget…


1. 浮窗的对齐方式

如下所示,TolyTooltip 提供了 12 种浮窗对齐方式,分为上下左右四组,每组有三种对齐方式。另外,默认提示框 支持气泡框,而且气泡的尖角位置会随着对其方式发生变化:

11.gif

使用方式非常简单,直接通过 TolyTooltip 组件嵌套在目标组件之上即可。其中:

  • textStyle 参数可以指定浮窗内文字样式。
  • padding 参数设置浮层弹框内边距。
  • placement 参数设置浮层弹框和目标组件的对其方式。
  • gap 参数设置浮层弹框和目标组件的距离。
  • message 参数设置浮层弹框文字内容。
TolyTooltip(
  textStyle: TextStyle(fontSize: 13,color: Colors.white),
  padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8),
  placement: TooltipPlacement.bottom,
  gap: 12,
  message: /// 提示信息,
  child: ///目标组件
),

提示框摆放的位置,通过 TooltipPlacement 枚举表示。有如下 12 种:

enum TooltipPlacement {
  top,
  topStart,
  topEnd,
  bottom,
  bottomStart,
  bottomEnd,
  left,
  leftStart,
  leftEnd,
  right,
  rightStart,
  rightEnd
}

2. 展示富文本

可以通过 richText 参数设置 InlineSpan 可以展示富文本。包括使用 WidgetSpan 在文字中嵌入组件。效果如下: :如果不知道 InlineSpan 是什么,可以阅读 《RichText》 组件的相关文章。

无标题项目.gif

const InlineSpan span = TextSpan(children: [
  TextSpan(text: '请通过此邮箱联系我们\n '),
  TextSpan(
    style: TextStyle(color: Colors.blue),
    text: '1981462002@qq.com ',
  )
]);

TolyTooltip(
  richMessage: span,
  placement: TooltipPlacement.top,
  padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  gap: 12,
  child: ///目标组件
);

3. 边界溢出自适应

边界溢出检测,并自动适应偏移功能,是花费我很大心力实现的。相比于绝对遵从对其方式而是提示框溢出边界,只能展示一部分,边界溢出自适应更加合理。如下所示:

  • Flutter 介绍 按钮的 Tooltip 对齐方式设置为 top,但当上方展示的区域不足时,自动转换为 bottom。
  • TolyUI 链接对齐方式设置为 left ,当上方展示的区域不足时,自动转换为 leftStart。

06.gif


4. 样式设置

TolyTooltip 提供了很多可配置的选项,比如背景色、填充模式等,让使用者可以更灵活地展示信息。另外通过设置最大高度,可以在弹框高度过高时允许滑动。效果如下:

07.gif

上面的第一个案例是取消气泡框效果: 将 decorationConfig 参数的 isBubble 置为 false 即可。另外,该配置参数中还可以设置背景色、填充模式、文字颜色:

image.png

  Widget buildTooltipDisplay1(){
    String message = '...';
    return TolyTooltip(
      message: message,
      placement: TooltipPlacement.top,
      textStyle: const TextStyle(fontFamily: '宋体',fontSize: 12,fontWeight: FontWeight.bold),
      decorationConfig: const DecorationConfig(
        isBubble: false,
        textColor: Colors.white,
      ),
      maxWidth: 250,
      padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      gap: 12,
      child: const TolyLink(
        text: '张风捷特烈', href: 'https://juejin.cn/user/149189281194766', onTap: jumpUrl,
      ),
    );
  }

三、Popover 的设计动机

当点击组件时,有时需要打开一个浮层弹框,并且在面板中进行交互。虽然 PopMenuButton 等组件可以展开浮层菜单栏,但是一方面浮层弹框的自定义灵活性很差,另一方面,仅展示一个浮层面板,并不是很符合菜单的语义。
比如下面微信和飞书中展开的面板,严格意义上来说不能称之为 菜单,但也不是提示信息。这就是 Popover 的设计动机:

通过交互,展开一个 支持交互 的浮层面板,并允许外界控制展示和隐藏

微信头像点击飞书文档
image.pngimage.png

可能有人会觉得 Tooltip 和 Popover 在功能上差不多。但它们在界面交互中 行使的职能 是有区别的。Tooltip 浮层弹框 只在于展示信息,目标组件的依旧可以形式自己的点击事件。比如AndroidStudio 中鼠标悬浮文件名时,展示详细的路径信息,属于 Tooltip 功能:

image.png

而 Popover 会可能会消耗目标组件的点击事件,弹出浮层弹框;另外该浮层 可以有消费事件 的需求。这两点是它和 Tooltip 的差异所在。比如 Photoshop 工具悬浮时展示的浮层面板,可以通过 Popover 展示:

image.png


四、Popover 的使用

Popover 的使用案例介绍可以网站访问 TolyUI 的 web 版 Flutter 应用。

组件/反馈组件/popover: toly1994.com/ui/#/widget…


1. TolyPopover 的基本使用

TolyPopover 通过 overlay 属性展示浮层面板;builder 方法可以回调控制器,控制器可以主动打开或关闭浮层。如下所示:

08.gif

左侧案例通过 MouseRegion 监听鼠标进入事件,通过控制器打开浮层:

TolyPopover(
  placement: Placement.top,
  maxWidth: 200,
  overlay: const DisplayPanel(),
  builder: (_, ctrl, __) {
    return MouseRegion(
      onEnter: (_)=> ctrl.open(),
      child: const DebugDisplayButton( info: 'Hover Me'),
    );
  },
);

中间的案例通过按钮的点击回调事件 onPressed 方法,通过控制器打开浮层:

TolyPopover(
  placement: Placement.top,
  maxWidth: 200,
  overlay: const DisplayPanel(),
  builder: (_, ctrl, __) {
    return DebugDisplayButton( info: 'Click Me', onPressed: ctrl.open);
  },
);

右侧的案例通过 GestureDetector 的长按回调事件 onLongPress 方法,通过控制器打开浮层。除此之外,双击事件,按下事件、抬起事件,甚至是自定义的交互手势,都可以通过控制器来打开或关闭浮层:

TolyPopover(
  placement: Placement.top,
  maxWidth: 200,
  overlay: const DisplayPanel(),
  builder: (_, ctrl, __) {
    return GestureDetector(
        onLongPress: ctrl.open, child: const Text('LongPress Me'));
  },
);

2. 浮层弹框中控制关闭

有时需要在浮层中控制浮层自身的关闭,而关闭浮层的关键在于控制器。也就是说,只要让浮层弹框感知到控制器,即可进行操作。此时可以将 overlay 入参升级为 overlayBuilder,来感知控制器:

09.gif

左侧案例的删除弹框,点击确定或取消后关闭浮层面板。弹框的内容由 DeletePanel 构建,将动画控制器传入其中即可,在点击按键时通过 ctrl 关闭浮层:

image.png

TolyPopover(
  placement: Placement.top,
  maxWidth: 200,
  overlayBuilder: (_,ctrl) => DeletePanel( ctrl: ctrl),
  builder: (_, ctrl, __) {
    return DebugDisplayButton(
      info: 'Delete',
      onPressed: ctrl.open,
    );
  },
);

右侧案例的添加按钮打开菜单也是类似,通过 DisplayMenu 展示菜单内容,构造时传入控制器。另外,和 TolytTooltip 一样,也可以通过 decorationConfig 来配置气泡框的装饰效果:

TolyPopover(
  placement: Placement.bottomEnd,
  maxWidth: 180,
  overlayBuilder: (_, ctrl) => DisplayMenu(ctrl),
  decorationConfig: const DecorationConfig(),
  builder: (_, ctrl, __) {
    return GestureDetector(
      onTap: ctrl.open,
      child: const Padding(
        padding: EdgeInsets.symmetric(horizontal: 4.0),
        child: Icon(Icons.add_circle_outline, color: Color(0xff666666)),
      ),
    );
  },
);

3. 自定义装饰和偏移

如下所示 TolyPopover 可以给使用者足够的发挥空间,来自定义面板内容以及装饰样式。其中通过 overlayDecorationBuilder 函数构造展示,目的是回调 PopoverDecoration 提供一些必要的参数,你可以根据它来更好的自定义装饰。offsetCalculator 也是如此,可以实现如下效果:

11.gif

左侧案例中,点击头像时展示面板信息。通过 overlayDecorationBuilder 自定义非气泡框的普通装饰;并通过 offsetCalculator 计算偏移量,让弹框左上角和头像的中心对齐:

TolyPopover(
  placement: Placement.rightStart,
  maxWidth: 260,
  overlay: const _DisplayPanel(),
  overlayDecorationBuilder: decorationBuilder,
  offsetCalculator: calculatorOffset,
  builder: (_, ctrl, __) {
    return GestureDetector(
      onTap: ctrl.open,
      child: const CircleAvatar(
        backgroundImage: AssetImage('assets/images/icon_head.webp'),
      ),
    );
  },
);

calculatorOffset 负责基于 Calculator 数据计算偏移量。它承载着目标组件尺寸、间隔、对齐方式、浮框尺寸四个数据。这里向下移动目标组件尺寸高度的一半,向左移动间隔加上目标组件尺寸宽度一半,即可让浮层左上角和目标组件中心对齐:

//// 计算偏移
Offset calculatorOffset(Calculator calculator){
  double x = calculator.boxSize.width / 2 + calculator.gap;
  double y = calculator.boxSize.height / 2;
  return Offset(-x, y);
}

decorationBuilder 负责自定义装饰,PopoverDecoration 会回调组件尺寸、对齐方式、气泡偏移量三个值。如果你需要自己定义气泡装饰,这些数据将会非常有用。不过这里使用的是普通的 BoxDecoration ,这些数据就没有什么作用了。

/// 自定义装饰
Decoration decorationBuilder(PopoverDecoration decoration){
  return BoxDecoration(
    color: Colors.white,
    border: Border.all(color: Colors.black.withOpacity(0.1)),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        offset: const Offset(0, 2),
        blurRadius: 6,
        spreadRadius: 0,
      )
    ],
    borderRadius: BorderRadius.circular(4),
  );
}

4. Popover 的 对齐方式

Popover 的 12 种浮窗对齐方式和 TolyTooltip 一样;另外指定的对齐方式在溢出边界后也可以自适应转变:

10.gif

TolyPopover(
  maxWidth: 200,
  placement: Placement.bottom, /// 指定对齐方式
  gap: 12,
  overlay: _DisplayPanel(title: info,),
  builder: (_,ctrl,__)=>
      DebugDisplayButton(info: buttonText ,onPressed: ctrl.open,),
),

尾声

这就是 TolyUI 带来的反馈组件的第一波,也是继响应式布局之后,推出的第二个模块。随着 tolyui 的逐步迭代,越来越多的新功能将会支持。感谢你关注 tolyui 的成长,如果喜欢,也希望你能在 github 中点赞支持~

github 开源地址: github.com/TolyFx/toly…
TolyUI 官方案例演示网站:toly1994.com/ui

image.png

TolyUI 的下一步会继续设计打造反馈组件,更多的精彩内容,敬请期待 ~