flutter 基础 —— 事件监听

事件机制:

  1. 命中测试的过程是从上层组件到下层组件,但是加入 HitTestResult 的顺序是从下到上,分发事件的顺序同加入顺序。
  2. 通常,若用户点击坐标不在当前节点的 size 范围内,则 hitTest 直接返回 false。
  3. 命中测试过程中,① 兄弟节点的加入顺序是倒序的,这个可以结合 Stack 组件来理解,即上层组件(children 中靠后)优先遍历。② 一旦有一个子节点的 hitTest 返回了 true,就会终止遍历。因为通常兄弟节点通常是不重叠的,且用户点击的坐标位置只会有一个节点,所以只要一个子节点命中了 hitTest 就返回 true,并且终止遍历。
  4. 节点自身是否通过命中测试的标志是它被添加到 HitTestResult 列表中,而不是它 hitTest 的返回值。但是 hitTest 的返回值会影响父组件是否被添加到 HitTestResult 中。

示例:

① RenderBox

bool hitTest(BoxHitTestResult result, { required Offset position }) {
  if (_size!.contains(position)) {
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      //添加到命中测试列表
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
  }
  return false;
}

// 通常会该重写该方法
@protected
bool hitTestSelf(Offset position) => false;

② RenderProxyBoxWithHitTestBehavior

// ColoredBox(初始化 behavior 是 HitTestBehavior.opaque)、
// Listener(初始化 behavior 是 HitTestBehavior.deferToChild) 均继承自该组件

@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;

③ IgnorePointer

@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
// 未调用 result.add,则当前节点没有命中测试;
// hitTest 返回 false(ignoring 默认为true),表示当前节点不参与测试,
// 如果有其它兄弟节点,则会继续测试下一个兄弟节点。
  return !ignoring && super.hitTest(result, position: position);
}

④ AbsorbPointer

@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
// 未调用 result.add,则当前节点没有命中测试;
// hitTest 返回 true(absorbing 默认为true),表示前节点已参与测试,
// 如果有其它兄弟节点,则不会继续测试下一个兄弟节点。
  return absorbing
      ? size.contains(position)
      : super.hitTest(result, position: position);
}

⑤ 自定义事件透传组件

代码:

class PassPointer extends SingleChildRenderObjectWidget {
  const PassPointer({this.onTap, super.child});

  final VoidCallback? onTap;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderPassPointer(onTap: onTap);
  }
}

class RenderPassPointer extends RenderProxyBox {
  RenderPassPointer({
    this.onTap,
    RenderBox? child,
  }) : super(child);

  final VoidCallback? onTap;

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {
    if (size.contains(position)) {
      // 将当前节点添加到 HitTestResult 表中,即通过命中测试
      result.add(BoxHitTestEntry(this, position));
      // 返回 false,表示当前节点不参与测试,则会继续测试下一个兄弟节点
      return false;
    } else {
      return super.hitTest(result, position: position);
    }
  }

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    if (event is PointerDownEvent) {
      onTap?.call();
    }
  }
}

// 展示效果
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Stack(
            alignment: Alignment.center,
            // 多个兄弟节点,事件的遍历从下到上
            children: [
              Container(
                width: 150,
                height: 150,
                color: Colors.yellow,
                child: InkWell(
                  onTap: () {
                    print('C');
                  },
                ),
              ),
              Listener(
                behavior: HitTestBehavior.translucent,
                onPointerDown: (e) => print('bbb'),
                child: IgnorePointer(
                  child: Container(
                    width: 100,
                    height: 100,
                    color: Colors.green,
                    child: InkWell(
                      onTap: () {
                        print('B');
                      },
                    ),
                  ),
                ),
              ),
              PassPointer(
                onTap: () => print("aaa"),
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.pink,
                  child: InkWell(
                    onTap: () {
                      print('A');
                    },
                  ),
                ),
              ),
            ],
          )),
    );
  }
}

posted on 2023-01-10 18:09  Lemo_wd  阅读(268)  评论(0编辑  收藏  举报

导航