欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Flutter 事件处理源码剖析

程序员文章站 2022-05-19 16:14:15
...

博主相关文章列表
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手势识别器的基类

Flutter 事件处理源码剖析
/// [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全栈式开发课程,请访问以下地址
Flutter全栈式开发之Dart 编程指南
Flutter 事件处理源码剖析

Flutter 全栈式开发指南

Flutter 事件处理源码剖析