Flutter 事件处理源码剖析

博主相关文章列表
Flutter 框架实现原理
Flutter 框架层启动源码剖析
Flutter 页面更新流程剖析
Flutter 事件处理源码剖析
Flutter 路由源码剖析
Flutter 安卓平台源码剖析(一)
Flutter 自定义控件之RenderObject

Flutter 事件处理源码剖析

事件都是由硬件收集起来的,然后传递给系统处理。在Flutter中,则是由平台层传递给Flutter引擎,再由引擎通知给上层应用处理。在ui.Window类中,通过onPointerDataPacket回调来通知上层。

事件的分发

结合前面的启动流程分析,我们知道Flutter框架层的事件源头在GestureBinding中,找到initInstances方法实现

/// [GestureBinding]


  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }

  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    // 将指针数据转换为逻辑像素,这样就可以以独立于设备的方式定义触摸斜率
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
      _flushPointerEventQueue();
  }

  void _flushPointerEventQueue() {
    assert(!locked);
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }

  void _handlePointerEvent(PointerEvent event) {
    HitTestResult hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent) {
      hitTestResult = HitTestResult();
        
      // 开始命中测试,会得到一个需要处理的控件成员列表
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      //  抬起和取消,不进行命中测试,移除。
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down) {
      // 因为在指针down 时发生的事件(如PointerMoveEvents)应该被派发到与其初始PointerDownEvent相同的地方,
      // 我们希望重新使用指针down时找到的路径,而不是每次都进行命中检测。
      hitTestResult = _hitTests[event.pointer];
    }

    if (hitTestResult != null ||
        event is PointerHoverEvent ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
       // 事件分发
      dispatchEvent(event, hitTestResult);
    }
  }

GestureBinding中的hitTest实现仅仅将自己添加到HitTestResultList中,由此可见此处并不是真正的逻辑

void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
}

在前面启动源码剖析时,我们知道WidgetsFlutterBinding混入了多个Binding,相当于多继承,而此处最先调用的是RendererBinding中的hitTest方法

flutter\lib\src\rendering\binding.dart

/// [RendererBinding]

void hitTest(HitTestResult result, Offset position) {
  assert(renderView != null);
  renderView.hitTest(result, position: position);
    
  // 最后调用GestureBinding的hitTest,将自己添加到列表的最末
  super.hitTest(result, position);
}

flutter\lib\src\rendering\view.dart

/// [RenderView]

bool hitTest(HitTestResult result, { Offset position }) {
  if (child != null)
    // 尝试将符合条件的 child 控件添加到 HitTestResult 里
    child.hitTest(BoxHitTestResult.wrap(result), position: position);
  // 添加自身  
  result.add(HitTestEntry(this));
  return true;
}

flutter\lib\src\rendering\box.dart

/// [RenderBox]

bool hitTest(BoxHitTestResult result, { @required Offset position }) {
  // 判断自身是否处于响应区域之内
  if (_size.contains(position)) { 
    // 尝试添加下级的 child 和自己。递归调用,从控件树自下而上的得到了一个相应控件列表
    // 控件树最下面的叶子节点则会处于列表的first位置
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
  }
  return false;
}

通过RendererBinding中的super.hitTest(result, position)调用,进入GestureBinding中的hitTest方法,将自己添加到列表的最末,接着通过dispatchEvent进行事件的分发处理。

flutter\lib\src\gestures\binding.dart

/// [GestureBinding]

  void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
    // 没有命中测试信息,则通过 pointerRouter.route 将事件分发到全局处理
    if (hitTestResult == null) {
      assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
        // 省略......
      }
      return;
    }
    for (final HitTestEntry entry in hitTestResult.path) {
      try {
        entry.target.handleEvent(event.transformed(entry.transform), entry);
      } catch (exception, stack) {
        // 省略......
      }
    }
  }

HitTestTarget有两个子类,GestureBindingRenderObject。这里的HitTestEntry.target对象主要是一个RenderObject,因此调用RenderObjecthandleEvent方法。我们知道,普通控件并不能响应触摸事件,只有几个特定的Widget可以专门处理事件,例如GestureDetector,同样的,RenderObject及其子类也并没有都实现handleEvent方法,其中主要处理事件的RenderObject子类是RenderPointerListener

另外,不要忘记hitTestResult的列表中最后添加的是GestureBinding自己,for循环遍历到最后调用的是GestureBinding.handleEvent方法

flutter\lib\src\rendering\proxy_box.dart

/// [RenderPointerListener]

  RenderPointerListener({
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerCancel,
    this.onPointerSignal,
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
    RenderBox child,
  }) : super(behavior: behavior, child: child);


@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
  assert(debugHandleEvent(event, entry));
  if (onPointerDown != null && event is PointerDownEvent)
    return onPointerDown(event);
  if (onPointerMove != null && event is PointerMoveEvent)
    return onPointerMove(event);
  if (onPointerUp != null && event is PointerUpEvent)
    return onPointerUp(event);
  if (onPointerCancel != null && event is PointerCancelEvent)
    return onPointerCancel(event);
  if (onPointerSignal != null && event is PointerSignalEvent)
    return onPointerSignal(event);
}

这里的handleEvent主要是一层包装,回调的是外部传进来的接口。我们知道Widget和RenderObject是存在一定的对应关系的,接下来,我们就需要找到GestureDetectorRenderPointerListener之间的关系。

/// [GestureDetector]

Widget build(BuildContext context) {
  final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};

  if (
    onTapDown != null ||
    onTapUp != null ||
    onTap != null ||
    onTapCancel != null ||
    onSecondaryTap != null ||
    onSecondaryTapDown != null ||
    onSecondaryTapUp != null ||
    onSecondaryTapCancel != null
  ) {
    gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
      () => TapGestureRecognizer(debugOwner: this),
      (TapGestureRecognizer instance) {
        instance
          ..onTapDown = onTapDown
          ..onTapUp = onTapUp
          ..onTap = onTap
          ..onTapCancel = onTapCancel
          ..onSecondaryTap = onSecondaryTap
          ..onSecondaryTapDown = onSecondaryTapDown
          ..onSecondaryTapUp = onSecondaryTapUp
          ..onSecondaryTapCancel = onSecondaryTapCancel;
      },
    );
  }

  if (onDoubleTap != null) {
    gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
      () => DoubleTapGestureRecognizer(debugOwner: this),
      (DoubleTapGestureRecognizer instance) {
        instance.onDoubleTap = onDoubleTap;
      },
    );
  }

  if (onLongPress != null ||
      onLongPressUp != null ||
      onLongPressStart != null ||
      onLongPressMoveUpdate != null ||
      onLongPressEnd != null) {
    gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
      () => LongPressGestureRecognizer(debugOwner: this),
      (LongPressGestureRecognizer instance) {
        instance
          ..onLongPress = onLongPress
          ..onLongPressStart = onLongPressStart
          ..onLongPressMoveUpdate = onLongPressMoveUpdate
          ..onLongPressEnd =onLongPressEnd
          ..onLongPressUp = onLongPressUp;
      },
    );
  }

 /// ......省略部分代码......

  return RawGestureDetector(
    gestures: gestures,
    behavior: behavior,
    excludeFromSemantics: excludeFromSemantics,
    child: child,
  );
}

GestureDetectorbuild方法中根据外部设置的回调,创建了大量的手势识别器对象,这些手势识别器都是GestureRecognizer的子类。GestureRecognizer类提供了一个基本的API,可以被那些与手势识别器一起工作的类使用,但不关心手势识别器本身的具体细节。最终,GestureDetector将这些手势识别器传入RawGestureDetector中包装。

RawGestureDetector继承自StatefulWidget,我们查看RawGestureDetectorState.build方法

/// [RawGestureDetector]

@override
Widget build(BuildContext context) {
  Widget result = Listener(
    onPointerDown: _handlePointerDown,
    behavior: widget.behavior ?? _defaultBehavior,
    child: widget.child,
  );
  if (!widget.excludeFromSemantics)
    result = _GestureSemantics(
      child: result,
      assignSemantics: _updateSemanticsForRenderObject,
    );
  return result;
}

RawGestureDetector内部又使用Listener包装了一层,并将_handlePointerDown传递给它的onPointerDown回调

/// [Listener]

Widget build(BuildContext context) {
  Widget result = _child;
  if (onPointerEnter != null ||
      onPointerExit != null ||
      onPointerHover != null) {
    result = MouseRegion(
      onEnter: onPointerEnter,
      onExit: onPointerExit,
      onHover: onPointerHover,
      opaque: false,
      child: result,
    );
  }
  result = _PointerListener(
    onPointerDown: onPointerDown,
    onPointerUp: onPointerUp,
    onPointerMove: onPointerMove,
    onPointerCancel: onPointerCancel,
    onPointerSignal: onPointerSignal,
    behavior: behavior,
    child: result,
  );
  return result;
}

Listener中,又再次将onPointerDown透传给_PointerListener类,而在该类中,创建了RenderPointerListener对象。最终,将RawGestureDetector中的_handlePointerDown回调传递给了RenderPointerListenerhandleEvent调用,到这里我们完成了GestureDetector控件到RenderObject.handleEvent方法的闭环。

/// [_PointerListener]

RenderPointerListener createRenderObject(BuildContext context) {
  return RenderPointerListener(
    onPointerDown: onPointerDown,
    onPointerMove: onPointerMove,
    onPointerUp: onPointerUp,
    onPointerCancel: onPointerCancel,
    onPointerSignal: onPointerSignal,
    behavior: behavior,
  );
}

事件的竞争

在事件处理时,一个常见的问题是,当同一个区域内有多个控件可以接受触摸时,应该将事件交给谁来处理呢?这就涉及到Flutter的事件竞争机制。

回到RawGestureDetector_handlePointerDown方法

void _handlePointerDown(PointerDownEvent event) {
  assert(_recognizers != null);
  for (final GestureRecognizer recognizer in _recognizers.values)
    recognizer.addPointer(event);
}

GestureRecognizeraddPointer方法用于注册一个可能与这个手势检测器相关的新指针。这个手势识别器的所有者调用addPointer(),其中包含了每个应该被考虑用于这个手势的PointerDownEvent。然后,手势识别器的责任是将自己添加到全局指针路由器(见PointerRouter),以接收这个指针的后续事件,并将该指针添加到全局手势竞技场管理器(见GestureArenaManager),以跟踪该指针。

也就是说,只有通过addPointer之后才能参与竞争。

/// [GestureRecognizer]

void addPointer(PointerDownEvent event) {
  _pointerToKind[event.pointer] = event.kind;
  if (isPointerAllowed(event)) {
    addAllowedPointer(event);
  } else {
    handleNonAllowedPointer(event);
  }
}

GestureRecognizer中,addPointer实际上调用的是子类的addAllowedPointer方法。由于不同手势识别器的处理逻辑不同,这里我们分析最简单的Tap手势,则按照继承关系,进入Tap手势识别器的基类

/// [BaseTapGestureRecognizer]

void addAllowedPointer(PointerDownEvent event) {
  if (state == GestureRecognizerState.ready) {
    _down = event;
  }
  if (_down != null) {
    super.addAllowedPointer(event); // 调用父类
  }
}

void startTrackingPointer(int pointer, [Matrix4 transform]) {
    // 调用父类
    super.startTrackingPointer(pointer, transform);
}

这里主要调用了其父类方法,单个指针手势识别器基类PrimaryPointerGestureRecognizer

/// [PrimaryPointerGestureRecognizer]

void addAllowedPointer(PointerDownEvent event) {
  // 开始跟踪指针事件,实际上调用的是OneSequenceGestureRecognizer中的实现
  startTrackingPointer(event.pointer, event.transform);
  if (state == GestureRecognizerState.ready) {
    // 将手势识别器状态改为 possible
    state = GestureRecognizerState.possible;
    primaryPointer = event.pointer;
    initialPosition = OffsetPair(local: event.localPosition, global: event.position);
    if (deadline != null)
      _timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
  }
}

首先调用了OneSequenceGestureRecognizer中实现的startTrackingPointer方法

/// [OneSequenceGestureRecognizer]

void startTrackingPointer(int pointer, [Matrix4 transform]) {
  // 将自己添加到全局指针路由器,由GestureBinding的handleEvent处理
  GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
  _trackedPointers.add(pointer);
  assert(!_entries.containsValue(pointer));
  _entries[pointer] = _addPointerToArena(pointer);
}

// 将事件加入竞技场
GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
        return _team.add(pointer, this);
    return GestureBinding.instance.gestureArena.add(pointer, this);
}

这里调用GestureBinding成员GestureArenaManageradd方法,将手势识别器加入竞技场

/// 在竞技场中添加一个新成员(如手势识别器)
GestureArenaEntry add(int pointer, GestureArenaMember member) {
  final _GestureArena state = _arenas.putIfAbsent(pointer, () {
    return _GestureArena();
  });
  state.add(member);
  return GestureArenaEntry._(this, pointer, member);
}

这里有几个类需要说明一下,理顺逻辑

  • GestureArenaManager :手势竞技管理器,它管理了整个竞争的过程,胜出的条件是 ,第一个竞技获胜的成员或最后一个不被拒绝的成员

  • GestureArenaEntry :封装手势事件竞技信息的实体,包含参与事件竞技的成员

  • GestureArenaMember:代表一个参加竞技场的对象。从_GestureArena接收回调,以在手势竞争中获胜或失败时通知该对象。 无论此成员被解决的原因是什么,此成员被添加到的每个竞技场都会调用 acceptGesturerejectGesture 中的一个。 例如,如果成员自己决胜了竞技场,则该成员仍会收到acceptGesture回调。GestureRecognizer类将它作为接口实现,因此竞技场的竞争其实就是 GestureRecognizer 之间的竞争。

  • _GestureArena:是GestureArenaManager 内的竞技场。它内部持有一个 GestureArenaMember 列表,官方解释

    如果有成员在竞技场开放时试图获胜,则成为 “急切获胜者”。当竞技场对新的参与者关闭时,我们会寻找一个"急切获胜者",如果有一个,那么我们会在那个时候解决这个问题。

class GestureArenaManager {
  final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
    
  /// ......省略部分代码......
}

class _GestureArena {
  final List<GestureArenaMember> members = <GestureArenaMember>[];
  bool isOpen = true;
  bool isHeld = false;
  bool hasPendingSweep = false;

  GestureArenaMember eagerWinner;

  void add(GestureArenaMember member) {
    assert(isOpen);
    members.add(member);
  }
}

abstract class GestureArenaMember {
  /// 获胜时调用
  void acceptGesture(int pointer);

  /// 失败时调用
  void rejectGesture(int pointer);
}

class GestureArenaEntry {
  GestureArenaEntry._(this._arena, this._pointer, this._member);

  final GestureArenaManager _arena;
  final int _pointer;
  final GestureArenaMember _member;

  /// 调用该成员要求胜利(含接受)或承认失败(含拒绝)
  /// 试图解决一个已经解决的竞技场的手势识别器是可以的
  void resolve(GestureDisposition disposition) {
    _arena._resolve(_pointer, _member, disposition);
  }
}

最后调用的是GestureBinding中的handleEvent

/// [GestureBinding]

void handleEvent(PointerEvent event, HitTestEntry entry) {
  // 触发指针事件路由器。即调用手势识别器中的 handleEvent方法
  // 此处通常不会处理 PointerDownEvent 事件
  pointerRouter.route(event);
  if (event is PointerDownEvent) {
     // 关闭该Down事件的竞技,尝试获得胜利。如果没有则继续到 MOVE 或者 UP中决胜
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    // 到 UP 事件了,强行解决竞技场的问题,让第一个成员获胜。
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

这里是通过PointerDownEventPointerUpEvent分别来控制竞技场的关闭和清扫。

/// [GestureArenaManager]

// 关闭该Down事件的竞技,尝试获得胜利
void close(int pointer) {
  final _GestureArena state = _arenas[pointer];
  if (state == null)
    return; // This arena either never existed or has been resolved.
  state.isOpen = false;

  _tryToResolveArena(pointer, state);
}

void _tryToResolveArena(int pointer, _GestureArena state) {
    if (state.members.length == 1) {
        // 只有一个成员,不需竞争,直接响应后续所有事件
        scheduleMicrotask(() => _resolveByDefault(pointer, state));
    } else if (state.members.isEmpty) {
        _arenas.remove(pointer);
    } else if (state.eagerWinner != null) {
        _resolveInFavorOf(pointer, state, state.eagerWinner);
    }
}

void _resolveByDefault(int pointer, _GestureArena state) {
    if (!_arenas.containsKey(pointer))
        return; // Already resolved earlier.

    final List<GestureArenaMember> members = state.members;
    _arenas.remove(pointer);
    state.members.first.acceptGesture(pointer);
}

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    _arenas.remove(pointer);
    for (final GestureArenaMember rejectedMember in state.members) {
        if (rejectedMember != member)
            rejectedMember.rejectGesture(pointer);
    }
    member.acceptGesture(pointer);
}

// 清扫竞技场,强制让第一个成员获胜
void sweep(int pointer) {
    final _GestureArena state = _arenas[pointer];
    if (state == null)
        return; // This arena either never existed or has been resolved.

    if (state.isHeld) {
        state.hasPendingSweep = true;
        return; // This arena is being held for a long-lived member.
    }

    _arenas.remove(pointer);
    if (state.members.isNotEmpty) {
        // 第一个成员获胜。
        state.members.first.acceptGesture(pointer);
        // 把坏消息告诉所有其他成员。
        for (int i = 1; i < state.members.length; i++)
            state.members[i].rejectGesture(pointer);
    }
}

只有一个成员时,直接胜利,响应后续事件。否则在Down事件中无法决胜,留待Up事件处理,即走sweep方法

/// [BaseTapGestureRecognizer]

void acceptGesture(int pointer) {
  super.acceptGesture(pointer);
  if (pointer == primaryPointer) {
    // 调用TapGestureRecognizer中的handleTapDown方法 ,处理onTapDown回调
    _checkDown();
    _wonArenaForPrimaryPointer = true;
    // 调用TapGestureRecognizer中的handleTapUp方法 ,处理onTapUp或onTap回调
    _checkUp();
  }
}

事件拦截

Flutter框架给我们提供了两个Widget来处理事件拦截。

  • AbsorbPointer

    是一个在命中测试期间吸收指针事件的控件,当absorbing为true时,此控件通过自身终止命中测试来防止其子树接收指针事件。 它仍然在布局过程中占用空间,并像往常一样绘制其子级。 它只是防止其子级成为定位事件的目标,而它本身可以响应事件

  • IgnorePointer

    是一个在命中测试时不可见的控件。当ignoring为真时,该控件(和它的子树)在命中测试中是不可见的。它在布局时仍会消耗空间,并像往常一样绘制它的子节点。它只是不能成为定位事件的目标,而它本身无法响应事件

flutter\lib\src\widgets\basic.dart

/// [AbsorbPointer]

class AbsorbPointer extends SingleChildRenderObjectWidget {

    const AbsorbPointer({
        Key key,
        this.absorbing = true,
        Widget child,
        this.ignoringSemantics,
    }): assert(absorbing != null),
    		super(key: key, child: child);
    

    RenderAbsorbPointer createRenderObject(BuildContext context) {
        return RenderAbsorbPointer(
            absorbing: absorbing,
            ignoringSemantics: ignoringSemantics,
        );
    }
}

flutter\lib\src\rendering\proxy_box.dart

/// [RenderAbsorbPointer]

class RenderAbsorbPointer extends RenderProxyBox {

  RenderAbsorbPointer({
    RenderBox child,
    bool absorbing = true,
    bool ignoringSemantics,
  }) : assert(absorbing != null),
       _absorbing = absorbing,
       _ignoringSemantics = ignoringSemantics,
       super(child);
    
  @override
  bool hitTest(BoxHitTestResult result, { Offset position }) {
    return absorbing
        ? size.contains(position)
        : super.hitTest(result, position: position);
  }
}

视频课程

如需要获取完整的Flutter全栈式开发课程,请 点击跳转
qr_adv

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程之路从0到1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值