IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Flutter 事件分发 -> 正文阅读

[移动开发]Flutter 事件分发

一 事件分发

以 Android 为例,所有的事件都是起原生源于 io.flutter.view.FlutterView 这个 SurfaceView 的子类,整个触摸手势事件实质上经历了 JAVA => C++ => Dart 的一个流程,无论是 Android 还是 IOS ,原生层都只是将所有事件打包下发,比如在 Android 中,手势信息被打包成 ByteBuffer 进行传递,最后在 Dart 层的 _dispatchPointerDataPacket 方法中,通过 _unpackPointerDataPacket 方法解析成可用的 PointerDataPacket 对象使用。

@pragma('vm:entry-point')
// ignore: unused_element
void _dispatchPointerDataPacket(ByteData packet) {
  if (window.onPointerDataPacket != null)
    _invoke1<PointerDataPacket>(window.onPointerDataPacket,    
   window._onPointerDataPacketZone, _unpackPointerDataPacket(packet));
}

_dispatchPointerDataPacket? 是Dart 端接收原生端传递过来的事件的一个方法,所有原生传递过来的事件都要由这个方法处理。

_invoke1 实际就是调用window.onPointerDataPacke 方法。window.onPointerDataPacke 指向的就是WidgetsFlutterBinding的_handlePointerEvent

  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
    _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) {
  assert(!locked);
  HitTestResult hitTestResult;
  if (event is PointerDownEvent || event is PointerSignalEvent) {
    hitTestResult = HitTestResult();
     //核心代码
    ///开始碰撞测试了,会添加各个控件,得到一个需要处理的控件成员列表
    hitTest(hitTestResult, event.position);
    if (event is PointerDownEvent) {
       //复用机制,抬起和取消,不用hitTest,移除
      _hitTests[event.pointer] = hitTestResult;
    }

  } else if (event is PointerUpEvent || event is PointerCancelEvent) {
    ///复用机制,只需要在down的时候需要检测,手指处于滑动中,不用hitTest
    hitTestResult = _hitTests.remove(event.pointer);
  } else if (event.down) {
  //move事件直接重用down事件的hitTestResult,避免每次都进行命中测试
    hitTestResult = _hitTests[event.pointer];
  }

  if (hitTestResult != null ||
      event is PointerHoverEvent ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
     ///开始分发事件,这个很重要
    dispatchEvent(event, hitTestResult);
  }
}

如上代码所示, GestureBinding 的 _handlePointerEvent 方法中主要是 hitTest 和 dispatchEvent: 通过 hitTest 碰撞,得到一个包含控件的待处理成员列表 HitTestResult,然后通过 dispatchEvent 分发事件并产生竞争,得到胜利者相应。

1.1?hitTest

??

hitTest 主要为了得到一个 HitTestResult ,这个 HitTestResult 内有一个 List<HitTestEntry>?,每个 HitTestEntry.target 都会存储一个控件的 RenderObject

而RenderObject 默认都实现了 HitTestTarget 接口,所以可以理解为: HitTestTarget 大部分时候都是 RenderObject ,不了解RenderObject ?的同学可以直接将这个类看成是控件Widget。

/// A concrete binding for applications based on the Widgets framework.
///
/// This is the glue that binds the framework to the Flutter engine.
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

事实上 hitTestHitTestable 抽象类的方法,而 Flutter 中所有实现 HitTestable 的类有 GestureBindingRendererBindingWidgetsFlutterBinding ?通过多继承关键字继承了这两个类,后面的会覆盖前面的方法,因此这里调用的是 RendererBindinghitTest?

@override
void hitTest(HitTestResult result, Offset position) {
  assert(renderView != null);
  //源码在下面
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);
}

RendererBinding.hitTest 中会执行 renderView.hitTest(result, position: position); 如上代码所示,

  bool hitTest(HitTestResult result, { Offset position }) {
    if (child != null)
      child.hitTest(BoxHitTestResult.wrap(result), position: position);
    result.add(HitTestEntry(this));
    return true;
  }

renderView.hitTest 方法内会执行 child.hitTest ,它将尝试将符合条件的 child 控件添加到 HitTestResult 里,最后把自己添加进去。

child的实际类型是RenderObjcet?,次数可以看成是最上层的Widget,类似android的DectorView

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

通过 _size.contains 判断自己是否属于响应区域,如果在自己的响应区域在通过hitTestChildrenhitTestSelf 尝试添加下级的 child 和自己添加进去,如此递归下去最终得到一个控件列表,最底下的 Child 在最上面

到此第一部分就介绍完了。

1.2 dispatchEvent

dispatchEvent?中主要是对事件进行分发,并且通过上述添加进去的?target.handleEvent?处理事件,如下代码所示,在存在碰撞结果的时候,是会通过循环对每个控件内部的handleEvent?进行执行。

void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
 ///如果没有碰撞结果,那么通过 `pointerRouter.route`
// 将事件分发到全局处理,
  if (hitTestResult == null) {
    try {
      pointerRouter.route(event);
    } catch (exception, stack) {
    }
    return;
  }
  ///上面我们知道 HitTestEntry 中的 target 是一系自下而上的控件
 ///以及 renderView 和 GestureBinding。
 /循环执行每一个的 handleEvent 方法
  for (final HitTestEntry entry in hitTestResult.path) {
    try {
      //transformed 主要是坐标的转换
      entry.target.handleEvent(event.transformed(entry.transform), entry);
    } catch (exception, stack) {

    }
  }
}

????

当命中测试结果为空时进行路由分发,当命中测试结果不为空时,就进行命中结果分发,handleEvent方法的实现我们来看一个比较典型的实现--RenderPointerListener中的handleEvent。RenderPointerListener是Listener(可以监听原始PointEvent的Widget)对应的RenderObject对象。

GestureDector 内部最终使用的就是Listener来接受事件的,Listener会在接受到down事件之后调用GestureDector 里面的Recongizer的addPointer方法。其余的事件例如滑动等等就不会分发给GestureDector。

? ?

RenderPointerListener直接把事件给到对应的回调。

我们这里以GestureDector 处理down 事件为例,上面的onPointerDown会调用到GestureDector创建的RawGestureDector里面的_handlePointerDown方法.

在RawGestureDector 创建的Listener的时候仅仅设置了onPointerDown事件的回调,其余的诸如move,up等回调都是没有设置的,后续move等事件都是Recognizer在ondown里面通过注册路由回调接受到的。

void _handlePointerDown(PointerDownEvent event) {
  assert(_recognizers != null);
  for (final GestureRecognizer recognizer in _recognizers.values)
//把每一个手势识别器添加进入到路由当中,这一步相当关键
//之后添加到路由之后,手势识别器才可以接收到后面的move事件
//手势识别器不是一个Widget
    recognizer.addPointer(event);
}

调用所有支持的手势识别器的addPoint方法,让手势识别器跟踪这个手势。
如此手势识别器才可以接收到后面的move等事件。

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

addAllowedPointer在GestureRecognizer中是空实现,我们先看一个简单的实现TapGestureRecognizer,也就是点击手势的识别器。

@override
void addAllowedPointer(PointerDownEvent event) {
  开始追踪id为pointer的事件序列
  startTrackingPointer(event.pointer, event.transform);
  if (state == GestureRecognizerState.ready) {
    state = GestureRecognizerState.possible;
    primaryPointer = event.pointer;
    initialPosition = OffsetPair(local: event.localPosition, global: event.position);
    if (deadline != null)
      _timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
  }
}

@protected
void startTrackingPointer(int pointer, [Matrix4 transform]) {
  //注册路由,后续的事件会传递第二个参数handlEvent这个方法处理
 //详情参考下一个handleEvent.
  GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
  _trackedPointers.add(pointer);
  assert(!_entries.containsValue(pointer));
  //加入到pointer的手势竞技场
  _entries[pointer] = _addPointerToArena(pointer);
}

首先通过addRoute 注册路由,这里关键的是第二个参数,其是一个方法,down 之后的所有事件都会传递到这个回调中处理,进而让GestureRecognizer去追踪这个事件序列。

其后调用了_addPointerToArena。

  GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
      return _team.add(pointer, this);
    return GestureBinding.instance.gestureArena.add(pointer, this);
  }
_addPointerToArena 主要是在手势竞技场添加一个GestureArenaMember。
  GestureArenaEntry add(int pointer, GestureArenaMember member) {
    final _GestureArena state = _arenas.putIfAbsent(pointer, () {
      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
      return _GestureArena();
    });
    state.add(member);
    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
    return GestureArenaEntry._(this, pointer, member);
  }

GestureArenaMember 有两个接口一个接受手势,一个拒绝手势。当竞技完成之后回通过acceptGesture 方法通知竞技成功者,其余失败的竞技者会被调用rejectGesture。

?

我们知道事件的传递是通过冒泡来进行传递的,HitTestResult的最上层是GestureBinding,所以循环中最后一个handleEvent 属于GestureBinding,我们看一下GestureBinding的handleEvent方法

@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
//将事件分发到注册了此次事件的路由,一般是由GestureRecognizer中
//void addPointer(PointerDownEvent event)方法进行注册
  pointerRouter.route(event);
  if (event is PointerDownEvent) {
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

其他的控件会在自己的handleEvent 里面通过pointerRouter? 添加路由回调(参见前面)。

通过pointerRouter路由之后,在PointerDownEvent时候就调用GestureArenaManager的close方法(禁止Recognizer再加入到手势竞技中)。

_GestureArena 就是一个竞技场.每一个手指都有一个竞技场,每一个竞技场内部可以包含多个竞争者.

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);
}

先将isOpen变为false(在add的时候首先就会判断isOpen),然后调用_tryToResolveArena方法,继续跟进

void _tryToResolveArena(int pointer, _GestureArena state) {
  if (state.members.length == 1) {
     //只有1个Recognizer,直接添加一个_resolveByDefault方法调用的task
    scheduleMicrotask(() => _resolveByDefault(pointer, state));
  } else if (state.members.isEmpty) {
//没有Recognizer,直接移除该pointer对应的_GestureArena
    _arenas.remove(pointer);
  } else if (state.eagerWinner != null) {
   //渴望的胜利者,在_GestureArena关闭的时候如果不为空会
  //直接作为胜利者
    _resolveInFavorOf(pointer, state, state.eagerWinner);
  }
}

?_tryToResolveArena 根据识别器的数量进行了不同的处理

1? ?只有一个识别器的的时候

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);
}

2 没有识别器的时候。

? ? ? 此时就是移出对应的手势。

3

????????3.1 有多个识别器同时存在一个eagerWinner 识别器。

如果一个手势试图在竞技场开放时(isOpen=true)获胜,它将成为一个带有“渴望获胜”的属性的对象。当竞技场关闭(isOpen=false)时,竞技场将寻找一个“渴望获胜”的对象成为新的参与者,如果这时候刚好只有一个,那这一个参与者将成为这次竞技场胜利的青睐存在。

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);
}

???????此时直接将eagerWinner 作为竞技获胜者,其余的识别器为失败者。? ? ? ?

? 3.2 有多个识别器同时没有eagerWinner 识别器。

? ? ? 这种情况代表在down 事件的时候还没有办法确定最终的竞技场的获胜者。最终的获胜者可能是在move 事件或者是up 事件的时候确定的。

? ? ? ? 3.2.1? up 事件

?当Up 事件的时候会调用到sweep 方法。

  void sweep(int pointer) {
    final _GestureArena state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    assert(!state.isOpen);
    if (state.isHeld) {
      state.hasPendingSweep = true;
      assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
      return; // This arena is being held for a long-lived member.
    }
    assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
    _arenas.remove(pointer);
    if (state.members.isNotEmpty) {
      // First member wins.
      assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
      state.members.first.acceptGesture(pointer);
      // Give all the other members the bad news.
      for (int i = 1; i < state.members.length; i++)
        state.members[i].rejectGesture(pointer);
    }
  }

在up 事件的时候我们选取了列表的第一个成员作为获胜者,列表第一个成员就是控件树的最底下的那个。

在识别器的acceptGesture 方法中会回调用户设置的回调方法,例如用户设置的点击响应。

这里以TapGestureRecognizer 为例子。

  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }
  void _checkUp() {
    if (!_wonArenaForPrimaryPointer || _up == null) {
      return;
    }
    handleTapUp(down: _down, up: _up);
    _reset();
  }

  @override
  void handleTapUp({PointerDownEvent down, PointerUpEvent up}) {
    final TapUpDetails details = TapUpDetails(
      globalPosition: up.position,
      localPosition: up.localPosition,
    );
    switch (down.buttons) {
      case kPrimaryButton:
        if (onTapUp != null)
            //调用用户设置的回调接口
          invokeCallback<void>('onTapUp', () => onTapUp(details));
        if (onTap != null)
          invokeCallback<void>('onTap', onTap);
        break;
      case kSecondaryButton:
        if (onSecondaryTapUp != null)
          invokeCallback<void>('onSecondaryTapUp',
            () => onSecondaryTapUp(details));
        break;
      default:
    }
  }

? ? ? 3.2.1? ?move事件

?pointRouter.route 会回调GestureRecognizer 注册的路由,一般对用的就是GestureRecognizer的

handleEvent(PointerEvent event) 方法。注意这个handleEvent 与entry.target.handleEvent不是同一个方法。

??

这里以DragGestureRecognizer 为例。

@override
void handleEvent(PointerEvent event) {
  assert(_state != _DragState.ready);
  if (!event.synthesized
      && (event is PointerDownEvent || event is PointerMoveEvent)) {
    final VelocityTracker tracker = _velocityTrackers[event.pointer];
    assert(tracker != null);
    tracker.addPosition(event.timeStamp, event.localPosition);
  }

  if (event is PointerMoveEvent) {
    if (event.buttons != _initialButtons) {
      _giveUpPointer(event.pointer);
      return;
    }
    if (_state == _DragState.accepted) {
      //如果已经接受了事件,更新滑动距离
      _checkUpdate(
        sourceTimeStamp: event.timeStamp,
        delta: _getDeltaForDetails(event.localDelta),
        primaryDelta: _getPrimaryValueFromOffset(event.localDelta),
        globalPosition: event.position,
        localPosition: event.localPosition,
      );
    } else {
    //还没有接受事件
      _pendingDragOffset += OffsetPair(local: event.localDelta, global: event.delta);
      _lastPendingEventTimestamp = event.timeStamp;
      _lastTransform = event.transform;
      final Offset movedLocally = _getDeltaForDetails(event.localDelta);
      final Matrix4 localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform);
      _globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
        transform: localToGlobalTransform,
        untransformedDelta: movedLocally,
        untransformedEndPosition: event.localPosition,
      ).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
      //判断滑动距离是否大于最小滑动距离
      if (_hasSufficientGlobalDistanceToAccept)
      //接受滑动事件
        resolve(GestureDisposition.accepted);
    }
  }
  if (event is PointerUpEvent || event is PointerCancelEvent) {
    _giveUpPointer(
      event.pointer,
      reject: event is PointerCancelEvent || _state ==_DragState.possible,
    );
  }
}

当水平或者竖直滑动了一段距离之后就会调用resolve(GestureDisposition.accepted)来接受手势,之后再滑动的时候就直接更新滑动。

  void resolve(GestureDisposition disposition) {
    final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
    _entries.clear();
    for (final GestureArenaEntry entry in localEntries)
      entry.resolve(disposition);
  }

这里entry 实际类型是GestureArenaEntry,

  void resolve(GestureDisposition disposition) {
    _arena._resolve(_pointer, _member, disposition);
  }

GestureArenaEntry 的resolve调用了竞技场的_resolve 方法。

 void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
    final _GestureArena state = _arenas[pointer];
    if (state == null)
      return; // This arena has already resolved.
    assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
    assert(state.members.contains(member));
    if (disposition == GestureDisposition.rejected) {
      state.members.remove(member);
      member.rejectGesture(pointer);
      if (!state.isOpen)
        _tryToResolveArena(pointer, state);
    } else {
      assert(disposition == GestureDisposition.accepted);
      if (state.isOpen) {
        //竞技场还没有关闭的时候
        state.eagerWinner ??= member;
      } else {
        assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
         //一般情况下
        _resolveInFavorOf(pointer, state, member);
      }
    }
  }

  void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
    _arenas.remove(pointer);
    for (final GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
        rejectedMember.rejectGesture(pointer);
    }
    member.acceptGesture(pointer);
  }
 

_resolveInFavorOf 主要是遍历所有的GestureArenaMember(可以看成是手势识别器的代理,因为识别器实现了这个接口)通知其余的手势识别器被拒绝了也就是竞技失败,最终的竞技场的获胜者是member 。

?

下面梳理一下整个流程

1? 当手指点下去的时候会根据点击的位置获取到这个位置下面所有的控件,最底下的控件在列表的最上面,在后面遍历的时候优先级越高,其中GestureBinding的handleEvent 会在最后调用。

2 收集完所有的控件之后,调用其handleEvent 方法,一般需要在这个方法里面注册自己的手势识别器以及通过addRoute 添加一个手势路由(主要是注册另一名为handleEvent 的回调方法),添加的路由会在每次事件的时候都被调用,通常在这个回调方法内部判断当前的手势是否符合自己的要求,如果符合就申请接受当前手势。

3? 最后GestureBinding 的handleEvent 方法先执行的所有的路由回调,在收到down 事件的时候调用了gestureArena.close(event.pointer)? 关闭竞技场并尝试确定经济场的获胜者,如果不能在down 事件确定获胜者那么可能会在move 事件确定一个获胜者。如果在move 也不能确定一个获胜者那么在up 事件的时候会强制指定列表的第一个成员为获胜者。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:48:06  更:2021-07-25 11:48:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 22:01:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码