我对此的想法(因为 Flutter Driver 和小部件测试不使用真正的点击)是在 Flutter 级别记录点击,即使用 Flutter 命中测试。
我将向您展示一个 widget,您可以将您的应用封装到 visualize 和 capture all水龙头。我写了a complete widget for this。
演示
这是将小部件包装在默认模板演示应用程序周围时的结果:
实施
我们想要做的很简单:react 以我们的小部件大小(整个应用程序是我们的子应用程序)对 所有 点按事件。
然而,它带来了一个小挑战:GestureDetector e.g.对它们做出反应后,不会让点击通过。因此,如果我们使用TapGestureRecognizer,我们要么无法对点击我们应用中的按钮的点击做出反应或我们将无法点击按钮(只会看到我们的指示)。
因此,我们需要使用我们的自己的渲染对象来完成这项工作。当您熟悉时,这并不是一项艰巨的任务 - RenderProxyBox 正是我们需要的抽象 :)
捕捉命中事件
通过覆盖hitTest,我们可以确保我们始终记录点击:
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (!size.contains(position)) return false;
// We always want to add a hit test entry for ourselves as we want to react
// to each and every hit event.
result.add(BoxHitTestEntry(this, position));
return hitTestChildren(result, position: position);
}
现在,我们可以使用handleEvent 来记录命中事件并可视化它们:
@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
// We do not want to interfere in the gesture arena, which is why we are not
// using regular tap recognizers. Instead, we handle it ourselves and always
// react to the hit events (ignoring the gesture arena).
if (event is PointerDownEvent) {
// Record the global position.
recordTap(event.position);
// Visualize local position.
visualizeTap(event.localPosition);
}
}
可视化
我不会告诉你细节(完整代码在最后):我决定为每个记录的命中创建一个AnimationController,并将其与本地位置一起存储。
由于我们使用的是RenderProxyBox,我们可以在动画控制器触发时调用markNeedsPaint,然后为所有记录的点击绘制一个圆圈:
@override
void paint(PaintingContext context, Offset offset) {
context.paintChild(child!, offset);
final canvas = context.canvas;
for (final tap in _recordedTaps) {
drawTap(canvas, tap);
}
}
代码
当然,我浏览了实现的大部分部分,因为您可以通读它们:)
代码应该是直截了当的,因为我概述了我使用的概念。
您可以找到the full source code here。
用法
用法很简单:
TapRecorder(
child: YourApp(),
)
即使在我的示例实现中,您也可以配置点击圈颜色、大小、持续时间等:
/// These are the parameters for the visualization of the recorded taps.
const _tapRadius = 15.0,
_tapDuration = Duration(milliseconds: 420),
_tapColor = Colors.white,
_shadowColor = Colors.black,
_shadowElevation = 2.0;
如果您愿意,可以将它们设为小部件参数。
测试
我希望可视化部分不辜负您的期望。
如果您想超越这一点,我确保水龙头是全局存储的:
/// List of the taps recorded by [TapRecorder].
///
/// This is only a make-shift solution of course. This will only be viable
/// when using a single [TapRecorder] because it is saved as a top-level
/// variable.
@visibleForTesting
final recordedTaps = <Offset>[];
您可以简单地访问测试中的列表来检查记录的点击:)
结束
我在实现这个过程中获得了很多乐趣,我希望它达到了您的期望。
该实现只是一个快速的临时实现,但是,我希望它可以为您提供将这个想法提升到一个好的水平所需的所有概念:)