Flutter原理篇:GestureDetector原理深度剖析及手势原理(上)

今天我们来讲讲GestureDetector的深度剖析,只有了解原理了,才能知道手势冲突如何解决以及如何更灵活的运用手势。
我们先来看看GestureDetector的内部结构

1.GestureDetector只是一个包装类,最终还是由ListenerRenderPointListener执行事件的操作

2.点击事件开始时会首先执行RawGestureDetector_handlePointDown方法。

//GestureDetector
class GestureDetector extends StatelessWidget {
  Widget build(BuildContext context) {
	if (onTapDown != null ||
        onTapUp != null ||
        .....
    ) {
      //包装相关的手势类(单击手势)
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(..........
        },
      );
    }
    if (onDoubleTap != null ||
        onDoubleTapDown != null ||
        onDoubleTapCancel != null
        .....
    ) {
      //包装相关的手势类(双击手势)
gestures[DoubleTapGestureRecognizer]=GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(..........
        },
      );
    }
    ........
	return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
}

//RawGestureDetector的state方法
class RawGestureDetectorState extends State<RawGestureDetector> {
  @override
  void initState() {
    super.initState();
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    _syncAll(widget.gestures);
  }
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
  	//手势保存
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
    _recognizers = <Type, GestureRecognizer>{};
    //遍历新手势,过滤旧手势。gestures是从GestureDetector传过来的新手势
    for (final Type type in gestures.keys) {
      //如果是旧的手势(oldRecognizers[type])跟新手势一样,就直接赋值
      //如果旧手势跟新手势不一样,则需要调用旧手势的构造函数并执行初始化方法。
      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
      gestures[type]!.initializer(_recognizers![type]!);
    }
    //对旧手势执行dispose()方法进行销毁。
    for (final Type type in oldRecognizers.keys) {
      if (!_recognizers!.containsKey(type)) {
        oldRecognizers[type]!.dispose();
      }
    }
  }
   @override
  Widget build(BuildContext context) {
  	//RawGestureDetector最终调用的是Listener,Listener才是重点需要关注的。
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      onPointerPanZoomStart: _handlePointerPanZoomStart,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    return result;
  }
}
//Listener的类
class Listener extends SingleChildRenderObjectWidget {
  @override
  RenderPointerListener createRenderObject(BuildContext context) {
    //主要的手势类,重点关注这个RenderPointerListener
    return RenderPointerListener(
      onPointerDown: onPointerDown,
      onPointerMove: onPointerMove,
      .......
      behavior: behavior,
    );
  }
}

abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
   //...省略100字
}

abstract class GestureRecognizer extends 
GestureArenaMember with DiagnosticableTreeMixin {
   //...省略100字
}

在点击事件进来时,事件的传递流程梳理

1.每个传进来的手势都会被打包成GestureRecognizer的相关子类,OneSequenceGestureRecognizer是我们最常用的子类,同时它也是众多子类的父类。

2.打包好的GestureRecognizer相关子类会缓存到RawGestureDetectorgestures变量中以供后面手势加入竞技场和竞争的时候使用到
3.RawGestureDetector每一次的初始化的时候都会对gestures列表进行更新,始终保持最新的缓存

4.这个RenderPointerListener是Listener的renderObject,这个类间接继承了HitTestTarget接口,其实所有的renderObject都实现了HitTestTarget这个接口。顾名思义,单击测试目标。后面手势竞争的时候都会对实现HitTestTarget的实例进行单击测试。

5.GestureRecognizer同时也继承了GestureArenaMember(竞技成员),所以子类也可以看作是竞技成员

下图是GestureRecognizer的部分继承关系

  //RenderPointerListener(RenderObject)
  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent) {
      //这里执行的就是上图的_handlePointDown方法
      return onPointerDown?.call(event);
    }
    if (event is PointerMoveEvent) {
      return onPointerMove?.call(event);
    }
    if (event is PointerUpEvent) {
      return onPointerUp?.call(event);
    }
    ....
  }

小结:这一部分主要讲述了手势事件如何封装成GestureRecognizer,并最终走到RenderPointerListenerhandleEvent对手势类进行处理进行处理。在此之前我们先看看GestureBinding的初始化监听。对于后面的手势竞争有非常大的帮助。

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  //创建手势竞技场(负责决出竞技胜者)
  final GestureArenaManager gestureArena = GestureArenaManager();
  //创建目标收集类(负责收集单击测试目标)
  final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};

  
}
//手势竞技场
class GestureArenaManager {
  //管理不同的成员管理类
  final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
}
//手势竞技成员管理
class _GestureArena {
  //每个成员管理类都持有一个竞技成员列表,即上面说的GestureRecognizer啦
  final List<GestureArenaMember> members = <GestureArenaMember>[];
  bool isOpen = true;
  bool isHeld = false;
}

1.我们最初在main类中执行runApp()方法的时候,就开始对GestureBinding。的相关监听进行初始化

2.同时创建手势竞技场GestureArenaManager负责管理不同的竞技场,每个竞技场又管理者不同的GestureArenaMember的成员

3.同时创建了目标收集器_hitTests针对不同的事件类型创建不同的HitTestResult(这就是真正的目标收集器,即收集实现了HitTestTarget接口的RenderObjectGestureBinding)单击测试目标收集器

4.注意,GestureBinding也是实现了HitTestTarget的单击目标测试,同上面所说的RenderObject一样会接受单击目标测试。

5.GestureBinding实现了_handlePointerEventImmediately这个方法,由androidios监听事件并传入,对事件进行进一步的处理。

手势处理主要分两个阶段
1.目标收集
2.手势竞争


一、目标收集

//GestureBinding的实现,监听底层收到的事件进行相关处理并传输
  void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
      //创建一个目标收集器。一般事件开始的时候都会先走这个,这是第一步。
      hitTestResult = HitTestResult();
      //单击测试,收集可以响应单机测试的实例
      hitTestInView(hitTestResult, event.position, event.viewId);
      if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      	//存储单击测试结果,后面可以使用到
        _hitTests[event.pointer] = hitTestResult;
      }
    } else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
      //移除本次事件
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down || event is PointerPanZoomUpdateEvent) {
      //其他类型的down事件,取出单击测试的结果(通过单击测试的实例)
      hitTestResult = _hitTests[event.pointer];
    }
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
       //分配事件给通过单击测试的实例(hitTestResult)
      dispatchEvent(event, hitTestResult);
    }
  }

  //RendererBinding
  @override
  void hitTestInView(HitTestResult result, Offset position, int viewId) {
 	//在RenderBinding中又调用了renderView的hitTest
    _viewIdToRenderView[viewId]?.hitTest(result, position: position);
    //这里最后才会调用GestureBinding的hitTestInView
    super.hitTestInView(result, position, viewId);
  }
  bool hitTest(HitTestResult result, { required Offset position }) {
    if (child != null) {
      //这里又开始触发子节点的hitTest
      child!.hitTest(BoxHitTestResult.wrap(result), position: position);
    }
    //最后将自身即根renderview加入单击测试结果
    result.add(HitTestEntry(this));
    return true;
  }
  //子节点的单击测试方法
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
  	//这里判断单击的position是否在_size的范围内(就是是否在子节点的范围内)
    if (_size!.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      	//子节点在此刻通过了单击测试,即在点击范围内,则加入到result中。
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }
//开始事件分发
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    if (hitTestResult == null) {
      try {
      	//hitTestResult为null说明是可能回调了
      	//PointerHoverEvent,PointerAddedEvent,PointerRemovedEvent
      	//由路由分发出去
        pointerRouter.route(event);
      } catch (exception, stack) {
      
    for (final HitTestEntry entry in hitTestResult.path) {
      try {
      	//执行所有目标收集到的单击测试实例的handleEvent方法。
        entry.target.handleEvent(event.transformed(entry.transform), entry);
      } catch (exception, stack) {
        
      }
    }
  }
//GestureDetector实现的RenderPointerListener(调用子节点的handleEvent)
void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent) {
      return onPointerDown?.call(event);
    }
    if (event is PointerMoveEvent) {
      return onPointerMove?.call(event);
    }
    //省略一万字....
}

目标收集流程

1.当事件下发到GestureBinding时,会分别对不同事件进行判断处理,其中有手势点击和手势抬起事件

2.当进入PointerDownEvent的时候,先进入Rendview根节点的hitTest方法调用hitTestChildren进行子节点遍历,判断子节点是否符合点击范围,如果符合则将自身加入hitTestResult单击测试列表中

3.当遍历完子节点后再单击测试Rendview根节点,只有hitTestSelf条件符合了才能将自身加入到单击测试列表hitTestResult中。

4.最后一步再将GestureBinding加入到单击测试列表中,因为GestureBinding也实现了HitTestTarget单击测试目标接口。都会进行测试。

5.当单击目标测试完成后,将进行事件分发,分发到各个RendObject(都实现了HitTestTarget方法)中,即调用每个实例的handleEvent方法。

6.handleEvent的第一次点击即PointerDownEvent终会执行RawGestureDetector的_handlePointerDown方法


至此,目标收集流程已经结束,现在开始事件竞争


二、手势竞争

手势收集完成后,就进入到了RawGestureDetector的_handlePointerDown方法

void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);
    //这是对前面我们收集的竞技成员进行遍历访问
    for (final GestureRecognizer recognizer in _recognizers!.values) {
      //给每个竞技成员跟踪点击事件
      recognizer.addPointer(event);
    }
  }
}
void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }
}
//由子类OneSequenceGestureRecognizer(这个比较常用)
void addAllowedPointer(PointerDownEvent event) {
    //开始跟踪事件处理
    startTrackingPointer(event.pointer, event.transform);
  }
}
@protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
    //省略部分代码...
    _entries[pointer] = _addPointerToArena(pointer);
  }
//在这里开始将当前的GestureArenaMember(就是上面的Recognizer)添加到竞技场gestureArena
GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null) {
      return _team!.add(pointer, this);
    }
    return GestureBinding.instance.gestureArena.add(pointer, this);
  }

1._recognizers最初在初始化的时候已经对不同的事件进行手势封装成GestureRecognizer的不同子类,现在就是开始用的时候了

2.往下走最终调用_addPointerToArena方法,即将GestureRecognizer的子类实现添加到GestureBindinggestureArena手势竞技场中进行手势竞争。

三、 决出最终胜者

由于目标收集中最后一个加入的是GestureBinding,所以GestureBinding也是实现了HitTestTarget接口

@override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      //关闭竞技场
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      //清理竞技场
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }
//关闭竞技场(只是做个标记)
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; // This arena has already resolved.
    }
    final List<GestureArenaMember> members = state.members;
    _arenas.remove(pointer);
    //取第一个作为胜者
    state.members.first.acceptGesture(pointer);
  }

1.前面子类GestureRecognizer添加进竞技场之后,在GestureBinding开始决出胜者,即可以响应点击事件的成员。

2.在竞技场关闭的时候开始决出胜者,也是事件开始的事件即PointerDownEvent事件,开始决策

3.手势成员只有一个的时候,直接取第一个手势为胜者,手势成员为空的时候,直接清理竞技场,手势成员已经有胜者的时候,直接确定竞技场胜者

void sweep(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null) {
      //竞技场为空直接返回。
      return;
    }
    assert(!state.isOpen);
    if (state.isHeld) {
      //竞技场被挂起,直接返回。一般是双击的时候会用到这个挂起
      state.hasPendingSweep = true;
      return; 
    }
    //移除竞技场
    _arenas.remove(pointer);
    //竞技场成员不为空
    if (state.members.isNotEmpty) {
      // First member wins.
      //直接取第一个成员作为胜者并调用其的acceptGesture
      state.members.first.acceptGesture(pointer);
      // 其他的败者则调用rejectGesture
      for (int i = 1; i < state.members.length; i++) {
        state.members[i].rejectGesture(pointer);
      }
    }
  }

只要竞技场没有被挂起或为空,且此时会有多个手势竞技成员,这时候会取第一个成员作为胜者,就是renderbox最里面的那个。下节重点讲解双击事件以及事件的灵活运用。

  • 49
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Flutter是一种用于构建跨平台移动应用程序的开源UI工具包。它使用Dart编程语言,并提供了一个丰富的组件库,使开发人员可以轻松地构建漂亮、流畅且高性能的应用程序。Flutter的实现原理涉及几个关键概念和流程。 首先,Flutter应用程序由一个个小部件(widget)构成。小部件是构建用户界面的基本元素,可以是按钮、文本框、图像等。小部件可以嵌套,并按照一定的层次结构组织在一起,形成复杂的用户界面。 其次,Flutter使用Skia图形引擎来绘制用户界面。Skia是一个跨平台的2D图形库,它提供了高效的绘图和渲染功能。Flutter将应用程序界面转化为一系列的绘图指令,然后通过Skia将这些指令渲染到屏幕上。 另外,Flutter应用程序是通过Dart虚拟机来执行的。Dart虚拟机负责解释和执行Dart代码,并管理应用程序的运行时环境。Flutter框架提供了丰富的API和工具,使开发人员可以轻松地编写和调试应用程序。 在具体的实现过程中,Flutter应用程序通常包含一个或多个FlutterActivity。FlutterActivity是一个Android Activity,它负责创建和管理Flutter的运行时环境。在FlutterActivity中,通过FlutterActivityDelegate处理核心流程,包括创建FlutterView或FlutterNativeView,并将其设置为应用程序的主视图。 总之,Flutter通过组件化的方式构建用户界面,使用Skia图形引擎渲染界面,并由Dart虚拟机执行应用程序。这种架构使得Flutter具有高性能、快速开发和良好的跨平台兼容性的特点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超盘守

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

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

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

打赏作者

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

抵扣说明:

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

余额充值