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

这篇具有很好参考价值的文章主要介绍了Flutter原理篇:GestureDetector原理深度剖析及手势原理(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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的部分继承关系

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

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

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

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);
    }
    //省略一万字....
}

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

目标收集流程

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

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

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

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

只要竞技场没有被挂起或为空,且此时会有多个手势竞技成员,这时候会取第一个成员作为胜者,就是renderbox最里面的那个。下节重点讲解双击事件以及事件的灵活运用。文章来源地址https://www.toymoban.com/news/detail-859776.html

到了这里,关于Flutter原理篇:GestureDetector原理深度剖析及手势原理(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Flutter 笔记 | Flutter 核心原理(三)布局(Layout )过程

    Layout(布局)过程主要是确定每一个组件的布局信息(大小和位置),Flutter 的布局过程如下: 父节点向子节点传递约束(constraints)信息,限制子节点的最大和最小宽高。 子节点根据约束信息确定自己的大小(size)。 父节点根据特定布局规则(不同布局组件会有不同的布

    2024年02月09日
    浏览(78)
  • 深入理解 Flutter 图片加载原理

    作者:京东零售 徐宏伟 来源:京东云开发者社区 随着Flutter稳定版本逐步迭代更新,京东APP内部的Flutter业务也日益增多,Flutter开发为我们提供了高效的开发环境、优秀的跨平台适配、丰富的功能组件及动画、接近原生的交互体验,但随之也带来了一些OOM问题,通过线上监控

    2024年02月12日
    浏览(37)
  • Flutter 的线程模型和异步原理

    本文字数:: 36130 字 预计阅读时间: 91 分钟 在 Android 应用中, 用户时常会遇到界面卡顿的情况,非常影响用户的体验。作为 Android 开发肯定都知道:应用在主线程里做了大量的耗时操作(例如文件读写, 数据库读写,网络访问,图片编解码等),就会导致 UI 不能及时刷新,

    2024年02月15日
    浏览(27)
  • Flutter笔记:Web支持原理与实践

    Flutter笔记 Web支持原理与实践 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 : 291148484@163.com CSDN:https://blog.csdn.net/qq_28550263/article/details/135037756 华为开发者社区:https://bbs.huaweicloud.com/blogs/418443 一个纯 Flutter 构建的 Web 示例站点(建设中): Flutter-Online Top http://

    2024年01月23日
    浏览(27)
  • Flutter视图原理之StatefulWidget,InheritedWidget

    在flutter项目中,StatelessWidget,StatefulWidget,InheritedWidget是常见的widget,今天通过源码分析下它们是怎么实现的。 对应的功能基本上都是在element中实现的,widget只是提供组件配置的作用,所以在讲解StatefulWidget,InheritedWidget的时候,主要还是分析对应的element的实现。 StatefulW

    2024年02月07日
    浏览(21)
  • SWD下载调试接口原理深度剖析

    由于我们公司自己需要开发烧录工具,本人通过google搜相关文档和看ARM公司的技术文档,终于实现了这个功能。该篇幅敢很自信的说把SWD理论讲的最浅显易懂的。 作为ARM嵌入式工程师,下载调试器都应该知道,但你真正了解SWD如何下载调试的么?ARM 芯片通过什么物理接口和

    2023年04月24日
    浏览(25)
  • 深入理解 Flutter 图片加载原理 | 京东云技术团队

    随着Flutter稳定版本逐步迭代更新,京东APP内部的Flutter业务也日益增多,Flutter开发为我们提供了高效的开发环境、优秀的跨平台适配、丰富的功能组件及动画、接近原生的交互体验,但随之也带来了一些OOM问题,通过线上监控信息和Observatory工具结合分析我们发现问题的原因

    2024年02月12日
    浏览(28)
  • 深度剖析动态规划算法:原理、优势与实战

    动态规划是一种优化技术,通常用于解决那些可以分解为子问题的问题。它的核心思想是将大问题分解成小问题,通过解决小问题来构建大问题的解。这种方法通常用于解决最优化问题,其中目标是找到最佳解决方案,通常是最大化或最小化某个值。 动态规划算法的核心原理

    2024年02月07日
    浏览(30)
  • Flutter 状态管理框架 Provider 和 Get 原理分析

    首先,为什么需要状态管理,这是因为 Flutter 基于 声明式 构建 UI ,使用状态管理的目的之一就是解决「声明式」开发带来的问题。 「声明式」开发是一种区别于传原生的方式,所以我们没有在原生开发中听到过状态管理,如何理解「声明式」开发呢? 「声明式」VS「命令

    2023年04月24日
    浏览(56)
  • TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11

    原文:Mobile Deep Learning with TensorFlow Lite, ML Kit and Flutter 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如何实现目标。——《原则》,生活原则 2.3.c 认证是任何应用中最突出的

    2023年04月24日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包