首页 > 解决方案 > 如何在 Flutter 中模拟波纹效果并执行按钮的 onPressed?

问题描述

我试图模仿在 Flutter 中的 MaterialButton 上点击波纹的行为。本质上,我希望能够调用一些任意函数(例如tapTargetWithEffect(GlobalKey target))并查看给定的按钮上的涟漪效应GlobalKey以及onPressed要执行的回调。

我在指针测试中看到可以通过分派指针事件WidgetsBinding来触发涟漪效应。如何执行onPressed回调?

标签: flutter

解决方案


这是如何实现这种效果的简单实现。

  1. 首先,找到分配给给定 GlobalKey 的 RenderBox
  2. 然后检查它是否是语义意义上的按钮(即可以按下/点击)
  3. 在 RenderBox 上执行命中测试以获取HitTestEntry给定指针位置下的小部件(对象)列表
  4. 查找onTap分配了回调的目标(这些将实现RenderSemanticsGestureHandler),首先获取并分配给tappable
  5. 查找onPointerDown分配了回调的目标(这些将实现RenderPointerListener),首先获取并分配给clickable
  6. 调用onTap回调来执行按钮的onPressed
  7. onPointerDown用适当的方式调用PointerDownEvent以显示波纹
    1. PointerDownEvent接受position参数,使其成为原件的中心RenderBox
  8. 通过在应用程序边界内的某处PointerDownEvent调度来取消所需的持续时间PointerMoveEventWidgetsBinding.instance.dispatchEvent
    1. 您必须在此之前执行命中测试

请注意,hitTest()函数接受BoxHitTestResult将包含命中测试的结果:)

在此处输入图像描述

示例实现:

  void tapTargetWithEffect(GlobalKey target) {
    final RenderBox renderBox = target.currentContext.findRenderObject();
    final child = renderBox as RenderSemanticsAnnotations;
    if (child.button) {
      final size = renderBox.size;
      final center = renderBox.localToGlobal(size.center(Offset.zero));
      final hitResult = BoxHitTestResult();
      child.hitTest(hitResult, position: size.center(Offset.zero));
      final pointerDown = PointerDownEvent(
        position: center,
      );
      final tappableL = hitResult.path
          .where((element) => element.target is RenderSemanticsGestureHandler)
          .map((element) => element.target as RenderSemanticsGestureHandler)
          .toList();
      final clickableL = hitResult.path
          .where((element) => element.target is RenderPointerListener)
          .map((e) => e.target as RenderPointerListener)
          .toList();

      if (tappableL.isEmpty) {
        print('No RenderSemanticsGestureHandler available');
        return;
      }

      if (clickableL.isEmpty) {
        print('No RenderPointerListener available');
        return;
      }

      final tappable = tappableL.first;
      final clickable = clickableL.first;
      clickable.onPointerDown?.call(pointerDown);
      tappable.onTap?.call();

      // cancel the pointer down event
      Future.delayed(Duration(milliseconds: 500)).then((value) {
        print('Cancelling the pointer down event');
        final result = BoxHitTestResult();
        WidgetsBinding.instance.hitTest(result, Offset.zero);
        WidgetsBinding.instance.dispatchEvent(PointerMoveEvent(), result);
      });
    }
  }

推荐阅读