【交互 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 都是方法名,逻辑可以看前面的源码分析。

目录
相关文章
|
7天前
|
容器
Flutter Widget 解析
Flutter Widget 解析
|
7天前
|
存储 容器
Flutter 有状态Widget 和 无状态Widget
Flutter 有状态Widget 和 无状态Widget
|
1月前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
79 8
|
1月前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
107 2
|
1月前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
88 1
|
2月前
|
容器
flutter:第一个flutter&Widget的使用 (二)
本文介绍了Flutter框架下的基本组件及其用法,包括简单的 Stateless Widget 如文本和按钮,以及更复杂的 StatefulWidget 示例。详细解释了如何使用 `context` 获取祖先小部件的信息,并展示了 `MaterialApp` 的属性及用途。此外,还探讨了 `StatefulWidget` 与 `StatelessWidget` 的区别,以及 `AppBar` 的常见属性配置方法。适合Flutter初学者参考学习。
|
1月前
|
Dart JavaScript 前端开发
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
Flutter 是 Google 开发的开源 UI 框架,用于快速构建高性能的移动、Web 和桌面应用。Flutter 通过 Widget 构建 UI,每个 UI 元素都是 Widget,包括文本、按钮、图片等。Widget 不仅描述外观,还描述行为,是不可变的。常见的 Widget 包括结构型(Container、Column、Row)、呈现型(Text、Image)、交互型(ElevatedButton)和状态管理型(StatefulWidget)。Flutter 与鸿蒙 Next 在组件化架构、开发语言、布局系统、性能和跨平台支持方面各有优势
77 0
|
5月前
Flutter-底部弹出框(Widget层级)
文章描述了如何在Flutter中使用DraggableScrollableSheet创建一个底部弹出框,同时保持其可手势滑动关闭。作者遇到问题并提出对原控件进行扩展,以支持头部和列表布局的滑动关闭功能。
201 0
|
6月前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
53 0
|
7月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析