【交互 widget】Flutter Listener

简介: 【交互 widget】Flutter Listener

image.png

大家好,我是17,今天的每日 widget 为大家介绍 Listener。

Listener 调用回调以响应 pointer 事件。Listener 是底层的 pointer 事件处理,并不涉及到手势,所以不会有竞争的问题。

源码分析

Listener 自身的代码很简单,只是包了一个皮,点击测试的逻辑是它的父类完成的。

代码所在类  RenderProxyBoxWithHitTestBehavior

@override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    bool hitTarget = false;
    if (size.contains(position)) {
      hitTarget =
          hitTestChildren(result, position: position) || hitTestSelf(position);
      if (hitTarget || behavior == HitTestBehavior.translucent) {
        result.add(BoxHitTestEntry(this, position));
      }
    }
    return hitTarget;
 }
 @override
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
复制代码

hitTest 是 Listener 最重要的逻辑。 result 是测试结果列表,只有添加到 result 里才能响应 pointer 事件。

  1. 判断点击位置是否在 size 内,如果不在就返回 false,不会响应 pointer 事件。
  2. 点击位置如果在 size 内,child 的 点击测试和 自身的点击测试有一个通过即为通过。
  3. behavior == HitTestBehavior.translucent 无论怎样测试都通过,但返回值不变。
  4. behavior == HitTestBehavior.opaque 测试一定会通过。返回值一定为 true。

如果是多 child 的 widget,hitTest 的逻辑是怎样的呢?

代码所在类 RenderBoxContainerDefaultsMixin

bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) {
    ChildType? child = lastChild;
    while (child != null) {
      // The x, y parameters have the top left of the node's box as the origin.
      final ParentDataType childParentData = child.parentData! as ParentDataType;
      final bool isHit = result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child!.hitTest(result, position: transformed);
        },
      );
      if (isHit) {
        return true;
      }
      child = childParentData.previousSibling;
    }
    return false;
  }
复制代码

在兄弟节点间,前面的节点先绘制,后面的节点后绘制,所以后面的会覆盖前面的,被覆盖的节点是不应该响应点击的,所以从最后一个 child 开始判断,如果 hitTest 通过,也就不用判断前面的了。

使用 Listener

如果不涉及到手势,只是响应 pointer 事件,Listener 再合适不过。

const Listener({
    super.key,
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerHover,
    this.onPointerCancel,
    this.onPointerPanZoomStart,
    this.onPointerPanZoomUpdate,
    this.onPointerPanZoomEnd,
    this.onPointerSignal,
    this.behavior = HitTestBehavior.deferToChild,
    super.child,
  })
复制代码

虽然响应的事件很多,但其实用起来都一样,我们就拿 onPointerDown 举例吧。需要体验的是 behavior,前面源码中已经分析过,behavior 对 pointer 事件会产生影响。

behavior 的默认值 HitTestBehavior.deferToChild,deferTo 的英文含义是遵从,实际的行为也确实如此,child hitTest 通过,就能响应 pointer 事件,否则没有任何响应。

为了方便看效果,我们自定义一个类。

class MyHitTest extends SingleChildRenderObjectWidget {
  const MyHitTest({super.key, super.child});
  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderMyHitTest();
  }
}
class RenderMyHitTest extends RenderProxyBox {
  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    return false;
  }
}
复制代码

在 hitTest 中直接返回  false,表明 hitTest 失败。我们来测试下,看看 Listener 还能否响应 pointer 事件。

Listener(
      onPointerDown: ((event) {
        print(event);
      }),
      child: MyHitTest(
        child: Container(
          width: 100,
          height: 100,
          color: Colors.blue,
        ),
      ))
复制代码

现在无论怎么点击都泥牛入海。

我们修改下 hitTest,让他 返回 true。

bool hitTest(BoxHitTestResult result, {required Offset position}) {
    return true;
  }
复制代码

现在可以响应点击了。但是 MyHitTest 自身并没有加入到点击列表中,所以自身是不能响应 pointer 事件的。

我们做个实验来验证这一点,override handleEvent ,看看能否接收到 event。

class RenderMyHitTest extends RenderProxyBox {
  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    print(event);
  }
  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    return true;
  }
}
复制代码

结果无法接收到 event,虽然  hitTest 已经成功,但这仅仅是表明上层可以响应 pointer 事件,MyHitTest 自己是不能的。

为了能让 MyHitTest 也能响应 pointer 事件,把它加到列表中就好了。

@override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    result.add(HitTestEntry(this));
    return true;
  }
复制代码

关于 HitTestBehavior.deferToChild 的效果我们都测试完成了,下面看下 HitTestBehavior.opaque 的效果。因为代码不多,下面给出完整代码。

class MyHitTest extends SingleChildRenderObjectWidget {
  const MyHitTest({super.key, super.child});
  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderMyHitTest();
  }
}
class RenderMyHitTest extends RenderProxyBox {
  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    return false;
  }
}
Listener(
  behavior: HitTestBehavior.opaque,
  onPointerDown: ((event) {
    print(event);
  }),
  child: MyHitTest(
    child: Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
));
复制代码

注意 hitTest 返回 false。 HitTestBehavior.opaque 的作用就是:hitTestSelf 一定成功,能响应 pointer 事件。

HitTestBehavior.translucent 也能让自身能响应 pointer 事件,但 hitTest 的结果取决于 hitTestChildren 与 hitTestSelf 的结果,这个结果会影响上层能否响应 pointer 事件。

hitTestSelf,hitTestChildren 都是方法名,逻辑可以看前面的源码分析。

目录
相关文章
|
1月前
Flutter-底部弹出框(Widget层级)
文章描述了如何在Flutter中使用DraggableScrollableSheet创建一个底部弹出框,同时保持其可手势滑动关闭。作者遇到问题并提出对原控件进行扩展,以支持头部和列表布局的滑动关闭功能。
70 0
|
2月前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
22 0
|
3月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
3月前
|
Dart 前端开发 Android开发
【Flutter前端技术开发专栏】Flutter与原生代码的集成与交互
【4月更文挑战第30天】本文探讨了如何在Flutter中集成和交互原生代码,以利用特定平台的API和库。当需要访问如蓝牙、特定支付SDK或复杂动画时,集成原生代码能提升效率和性能。集成方法包括:使用Platform Channel进行通信,借助现有Flutter插件,以及Android和iOS的Embedding。文中通过一个电池信息获取的例子展示了如何使用`MethodChannel`在Dart和原生代码间传递调用。这些技术使开发者能充分利用原生功能,加速开发进程。
72 0
【Flutter前端技术开发专栏】Flutter与原生代码的集成与交互
|
3月前
|
开发框架 前端开发 搜索推荐
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
【4月更文挑战第30天】探索Flutter的自定义Widget与渲染流程。自定义Widget是实现复杂UI设计的关键,优点在于个性化设计、功能扩展和代码复用,但也面临性能优化和复杂性管理的挑战。创建步骤包括设计结构、定义Widget类、实现构建逻辑和处理交互。Flutter渲染流程涉及渲染对象树、布局、绘制和合成阶段。实践案例展示如何创建带渐变背景和阴影的自定义按钮。了解这些知识能提升应用体验并应对开发挑战。查阅官方文档以深入学习。
49 0
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
|
3月前
|
JavaScript 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
【4月更文挑战第30天】本文探讨了Flutter的Widget和状态管理。Widget是Flutter构建UI的基础,分为有状态和无状态两种。状态管理确保UI随应用状态变化更新,影响应用性能和可维护性。文章介绍了`setState`、`Provider`、`Riverpod`、`Bloc`和`Redux`等状态管理方法,并通过计数器应用展示了其实现。选择合适的状态管理策略对高效开发至关重要。
37 0
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
|
3月前
|
编解码 算法 开发者
Flutter的布局系统:深入探索布局Widget与布局原则
【4月更文挑战第26天】Flutter布局系统详解,涵盖布局Widget(Row/Column、Stack、GridView/ListView、CustomSingleChildLayout)和布局原则(弹性布局、约束优先、流式布局、简洁明了)。文章旨在帮助开发者理解并运用Flutter的布局系统,创建适应性强、用户体验佳的界面。通过选择合适的布局Widget和遵循原则,可实现复杂且高效的UI设计。
|
3月前
|
前端开发 开发者 UED
Flutter的自定义Painter:深入探索自定义绘制Widget的技术实现
【4月更文挑战第26天】Flutter的自定义Painter允许开发者根据需求绘制独特UI,通过继承`CustomPaint`类和重写`paint`方法实现。在`paint`中使用`Canvas`绘制图形、路径等。创建自定义Painter类后,将其作为`CustomPaint` Widget的`painter`属性使用。此技术可实现自定义形状、渐变、动画等复杂效果,提升应用视觉体验。随着Flutter的进化,自定义Painter将提供更丰富的功能。
|
3月前
|
开发框架 搜索推荐 Android开发
Flutter的Widget基础:概念、分类与深入探索
【4月更文挑战第26天】Flutter Widget详解:基础、分类与工作原理。Widget是Flutter UI的核心,描述界面外观而非直接渲染。分为基础、布局、可滚动及状态管理四大类。基于响应式编程,状态变化时自动更新UI。了解其概念、分类和原理,能助开发者高效构建精美应用。随着Flutter生态发展,Widget系统潜力无限。
|
11月前
|
Dart 前端开发 开发工具
谷歌移动UI框架Flutter教程之Widget
谷歌移动UI框架Flutter教程之Widget