Flutter 之 原始指针事件处理 (四十七)

2022-05-03  本文已影响0人  maskerII

1. 命中测试简介

在移动端,各个平台或UI系统的原始指针事件模型基本都是一致,即:一次完整的事件分为三个阶段:手指按下、手指移动、和手指抬起,而更高级别的手势(如点击、双击、拖动等)都是基于这些原始事件的。

当指针按下时,Flutter会对应用程序执行命中测试(Hit Test),以确定指针与屏幕接触的位置存在哪些组件(widget), 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件,然后从那里开始,事件会在组件树中向上冒泡,这些事件会从最内部的组件被分发到组件树根的路径上的所有组件,这和Web开发中浏览器的事件冒泡机制相似, 但是Flutter中没有机制取消或停止“冒泡”过程,而浏览器的冒泡是可以停止的。注意,只有通过命中测试的组件才能触发事件

2. Listener 组件

Listener 定义

  const Listener({
    Key? key,
    this.onPointerDown, // 手指按下回调
    this.onPointerMove, // 手指移动回调
    this.onPointerUp, // 手指抬起回调
    this.onPointerHover, // 悬停
    this.onPointerCancel, // 触摸事件取消回调
    this.onPointerSignal, // 
    this.behavior = HitTestBehavior.deferToChild,
    Widget? child,
  })

示例


class MSListenerDemo1 extends StatefulWidget {
  const MSListenerDemo1({Key? key}) : super(key: key);

  @override
  State<MSListenerDemo1> createState() => _MSListenerDemo1State();
}

class _MSListenerDemo1State extends State<MSListenerDemo1> {
  PointerEvent? _event;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("ListenerDemo1")),
      body: Center(
        child: Listener(
          onPointerDown: (PointerDownEvent event) =>
              setState(() => _event = event),
          onPointerMove: (PointerMoveEvent event) =>
              setState(() => _event = event),
          onPointerUp: (PointerUpEvent event) => setState(() => _event = event),
          child: Container(
            color: Colors.amber,
            width: 200,
            height: 200,
            alignment: Alignment.center,
            child: Text("${_event?.localPosition ?? ''} "),
          ),
        ),
      ),
    );
  }
}

image.png

手指在橘黄色矩形区域内移动即可看到当前指针偏移
当触发指针事件时,对应的事件会回调,参数 PointerDownEvent、 PointerMoveEvent、 PointerUpEvent 都是PointerEvent的子类。

注意 Pointer,即“指针”, 指事件的触发者,可以是鼠标、触摸板、手指。

PointerEvent类中包括当前指针的一些信息,如:

3. 忽略指针事件

使用IgnorePointer和AbsorbPointer来忽略指针事件

IgnorePointer和AbsorbPointer,这两个组件都能阻止子树接收指针事件,不同之处在于AbsorbPointer本身会参与命中测试,而IgnorePointer本身不会参与,这就意味着AbsorbPointer本身是可以接收指针事件的(但其子树不行),而IgnorePointer不可以

示例 IgnorePointer


class MSListenerDemo2 extends StatelessWidget {
  const MSListenerDemo2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("ListenerDemo2")),
      body: Center(
        child: Listener(
          onPointerDown: (event) => print("out"),
          child: IgnorePointer(
            child: Listener(
              onPointerDown: (event) => print("in"),
              child: Container(
                color: Colors.amber,
                width: 200,
                height: 200,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

点击Container时,无点击事件日志打印。
由于Container在IgnorePointer子树上,所以不会响应指针事件,所以日志不会输出"in",又由于IgnorePointer不会参与命中测试,因而日志不会输出"out"

示例2 AbsorbPointer


class MSListenerDemo3 extends StatelessWidget {
  const MSListenerDemo3({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSListenerDemo3")),
      body: Center(
        child: Listener(
          onPointerDown: (event) => print("out"),
          child: AbsorbPointer(
            child: Listener(
              onPointerDown: (event) => print("in"),
              child: Container(
                color: Colors.amber,
                width: 200,
                height: 200,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

点击Container时,日志会输出"out"
由于Container在AbsorbPointer的子树上,所以不会响应指针事件,所以日志不会输出"in",但AbsorbPointer本身是可以接收指针事件的,所以会输出"out"

参考:https://book.flutterchina.club/chapter8/listener.html

上一篇 下一篇

猜你喜欢

热点阅读