cocos源码二 《触摸事件系统》

概述:

DOM的输入事件通过CCInputManager转化成cocos的输入事件,由CCEventManager 分发给监听器。

监听器在通过回调函数(begin/move/end/cancel)告知事件派发对象(eventTarget)派发事件。

重要类:

event:事件信息对象,主要有触摸信息、鼠标信息、键盘信息等。

eventTarget: 是一个事件派发的对象,有注册,删除和派发等功能,等同于订阅-发布模式。

eventListener:是一个事件监听器的对象,主要用于创建触摸、鼠标、键盘等监听器。包含了类型、回调函数、优先级等。

eventManager:是一个管理监听器的类,主要是对监听器进行排序、分发等。

eventManager

属性:

了解属性可以很好的知道这个类都有做了那些操作,结合函数可以知道是监听器的存储方式,划分监听器的,对监听器都会做什么处理。

  _listenersMap: {}, //key:listenerID  value:_EventListenerVector
  _priorityDirtyFlagMap: {},//用来存储所有脏数据标记位的事件监听器,key:listenerID value:boolean
  _nodeListenersMap: {},//key:node._id,value:listeners[]  存储节点的所有事件监听器
  _toAddedListeners: [],//存储将要添加的事件监听器
  _toRemovedListeners: [],//存储将要删除的事件监听器
  _dirtyListeners: {},//key:listenerID  value: boolean
  _inDispatch: 0,//是否正在处理事件分派标记。 0 表示没有正在处理分派事件
  _isEnabled: false,//事件管理器是否开启
  _currentTouch: null,//当前正在处理的触摸事件对象
  _currentTouchListener: null,//当前正在处理的事件监听器对象
  _internalCustomListenerIDs: [],//用来存储自定义事件监听器ID

实现功能描述:

  1. 该类是监听器的管理者,因事件监听器有多种类型(总共五个),因此会对每种类型进行管理。
  2. 因事件监听器有触发的先后,所以在对事件监听器管理的时候加入了优先级概念。分为固定优先级、场景图优先级(渲染顺序)。

所以每种事件监听器类型,都有下列存储结构是:

_listenersMap:

  1. _fixedListeners 用于存储固定优先级的监听器(自定义监听器的优先级)
  2. _sceneGraphListeners 用于存储场景图优先级的监听器(就是node的)
  3. gtOIndex 用于标记固定优先级小于大于0的界限索引值。因为场景图优先级默认等于0,自定义优先级可=0

事件监听器触发顺序如下:

  1. 自定义优先级 priority<0
  2. 场景图优先级 listener 触发
  3. 自定义优先级 priority>=0的listener 最后触发。(gtOIndex的索引等于排序后监听器的priority等于0时在数组中的索引。)

自定义优先级时,priority值越小,优先级越高,值越小,优先级越低。场景图优先级_localZOrder越大,优先级越高。反之优先级越低。

  1. 为了可以管理节点上所有的事件监听器,有一个以下数据结构

_nodeListenersMap:

该数据存储着节点的所有类型的监听器,方便对节点上的所有监听器进行选择性的暂停、恢复、删除等功能。

在分发事件监听器时有个很重要的排序过程,其目的为了确定事件监听器的触发先后顺序。具体过程如下:

与排序相关的几个属性:

  1. _priorityDirtyFlagMap:这个map映射存储的是事件监听器类型与脏标记的映射。key=ListenerID;value=dirtyFlag;
  2. _dirtyListeners:暂时记录了有新的场景图优先级的监听器添加,或者是恢复了被暂停的监听器。key=Listener; value=boolean;

因为每种监听器都有固定优先级和场景图优先级之分。如果有新的事件监听器添加或者恢复,都会影响到新监听器相同类型的map数据排序顺序。

所以在每次dispatch的之前,都会对脏数据进行更新,暂时标记那种类型的监听器脏了。在具体的分发之前,会依据标记进行重新排序后再分发。

排序完成之后,_priorityDirtyFlagMap的value值都会标记位非脏数据。

同时有个optimize 点,只在真正分发时,进行一次排序,并非在加入监听器或恢复监听器时排序。

另外需要注意的是,节点的zIndex值被修改后,不单单影响渲染顺序,同时也会影响监听器的分发优先级。(sortAllchildren)

监听器的排序代码解析

  1. 自定义优先级排序算法就是根据设置的priority进行升序排序。priority 数值越小,越早被分发。
  2. 以下是场景图优先级的降序排序算法,层级越高,越早触发,与自定义优先级相反。

其主要考虑的问题又如下几个:1,祖父孙关系。2,非同父关系。3,同父关系

/**
sort的compare函数。降序
compare(a,b){
  if(a>b)return -1;
  if(a<b)return 1;
  return 0;
} 
**/
_sortEventListenersOfSceneGraphPriorityDes: function (l1, l2) {
    let node1 = l1._getSceneGraphPriority(),
      node2 = l2._getSceneGraphPriority();
    //此l1优先
    if (!l2 || !node2 || !node2._activeInHierarchy || node2._parent === null)
      return -1;
    //l2 优先
    else if (!l1 || !node1 || !node1._activeInHierarchy || node1._parent === null)
      return 1;
    /**
     1,如果是祖孙关系节点,则层级较浅的节点a会在查询一圈之后与层级较深节点b同时汇合在自己初始位置
     2,如果是非祖孙关系节点,与上述一致,层级较浅的节点a优先到达"祖父"节点为空,到达后从深层级节点初始位置开始最终循环d-s后,ab的祖父节点相同。
     3,如果是同父节点 直接退出
     循环总次数计算:node1循环次数为空次数等于a,node2循环次数为空次数等于b
     const max = Math.max(a,b);
     const min = Math.max(a,b);
     第一次循环min次之后,其中一个节点开始从另一个节点的起始位置开始。接着在循环(max-min)次之后,
     较深节点会再循环一个差值之后与层级浅的节点 有着共同的祖父节点。总循环次数等于 max。
    **/
    let p1 = node1, p2 = node2, ex = false;
    while (p1._parent._id !== p2._parent._id) {
      p1 = p1._parent._parent === null ? (ex = true) && node2 : p1._parent;
      p2 = p2._parent._parent === null ? (ex = true) && node1 : p2._parent;
    }
    //node1 与node2 是祖父孙关系,
    //同祖父孙关系,较浅的节点a会一圈之后在自己初始位置与较深节点b重合。
    if (p1._id === p2._id) {
      if (p1._id === node2._id)// p1不等于node1(l1),p1比较深 l1优先
        return -1;
      if (p1._id === node1._id)//p1等于node1,p1比较浅,l2优先
        return 1;
    }
    //到这一步已经确定不是祖孙关系了,那就存在 同父或不同父
    //1,ex=true;p1与p2 是不同父节点,直接对比节点_localZOrder。
    //2,ex=false; 是同父节点,因为降序所以反向减
    return ex ? p1._localZOrder - p2._localZOrder : p2._localZOrder - p1._localZOrder;
  }

这里需要注意的是:_localZOrder 与zIndex、siblingIndex 区别

  1. _localZOrder 是一个32位的数字,前16存储着zIndex节点顺序,后16位存储着子节点相对于在父节点下的索引位置。
  2. 渲染顺序由_localZOrder 决定,节点排序是比较_localZOrder值进行升序排序。
  3. _localZOrder 前16位存储的是zIndex,后16位存储的事siblingIndex(setSiblingIndex)。
  4. zIndex权重比siblingIndex大,如果zIndex 相同,渲染顺序由siblingIndex决定。反之如果修改了zIndex,再次修改siblingIndex毫无效果。
  5. 修改zIndex时,_localZOrder值是在下一帧才会改变,当前帧并不会改变。

在事件监听器触发的过程中,可能会添加新的监听器或者删除监听器。因此有了下列属性

_toAddedListeners:当在添加新的监听器时,遇到正在触发事件监听器时,新的监听器或暂存在该数组中。

_toRemovedListeners:当在添加新的监听器时,遇到正在触发事件监听器时,新的监听器或暂存在该数组中。

_inDispatch:正在分发时>1; 不在分发时=0;

什么时候这些暂存的数据与总数据合并呢?每帧的时候都会去检测是否有暂存数据存在,如果有则与总数据合并(即添加、删除)。

监听器的吞噬与不吞噬,监听的的吞噬情况由三种条件控制

  1. 事件的stopPropagation、stopPropagationImmediate 如果事件设置了停止派发,则监听器的不吞噬也会暂停,其他监听器也不会接受到信息。
  2. 节点的hitTest 返回值 与 监听器的 swallowTouches 值设置。
  3. swallowTouches=true;吞噬 只会分发到自身监听器其他监听器不会被分发。
  4. swallowTouches=false; 不吞噬 下一个监听器也会被分发 依次循环, 直到有一个监听器设置吞噬才不会往下传

参考代码:_dispatchEventToListeners,shouldStopPropagation

eventManager 分发流程

 CCEvent、CClistener、event-target

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值