Laya鼠标事件阅读
- 点击事件核心类:
MouseManager
和TouchManager
。
MouseManager
负责收集相关事件,进行捕获阶段和目标阶段。
TouchManger
负责处理和分发事件,进行冒泡阶段。- 捕获阶段:此阶段引擎会从stage开始递归检测stage及其子对象,直到找到命中的目标对象或者未命中任何对象;
- 目标阶段:找到命中的目标对象;
- 冒泡阶段:事件离开目标对象,按节点层级向上逐层通知,直到到达舞台的过程。
- 事件是由Canvas(浏览器控件等)发起,在
MouseManager
中注册处理。 MouseManager
在监听到事件后,会将事件添加到队列中,在stage进入下一帧时(Stage._loop
),一次性处理完所有事件。- 捕获、目标阶段核心逻辑:(从根节点开始,向子节点查找)
- 初始化Event。MouseManager维护一个唯一的Event对象,保留鼠标事件相关信息,如target、touchid等。
- 先判断sp是否有scrollRect,如果则scrollRect外,直接判定为没点到,返回false。
- 优先检测(
HitTestPrior
),并且没有点击到,则直接返回false。(width>0并且没有点击穿透的View,默认会被设置为HitTestPrior = true
) - 命中检测逻辑(
hitTest
)- 参数为被判断的sp和转换为相对与sp的鼠标坐标(通过
fromParentPoint
方法) - 如果有scrollRect,则先偏移鼠标点击位置。
- 如果sp的
hitArea
字段不为空,则返回sp的hitArea.isHit
结果。 - 如果
mouseThrough
为true,则检测子对象的实际大小进行碰撞。否则就使用(0,0,width,height)的矩形检测点是否在矩形内。
- 参数为被判断的sp和转换为相对与sp的鼠标坐标(通过
- 倒叙遍历子对象,从外向内检测,检测到后,直接跳过内部检测。
- 将目标对象和Event对象传递给
TouchManager
。
- 冒泡阶段核心逻辑:(从子节点开始,向根节点查找)
- 获取或创建的点击信息,用于检测拖拽、双击等。
- 从当前sp开始,递归收集父节点,按顺序放入数组中,子节点在前,父节点在后。
- 按照数组属性,对节点发送相关事件,如果事件被阻断(
event.stopPropagation
),则直接跳过所有父节点。事件的currentTarget为最先被点击的sp。
- 鼠标事件触发后,参数默认为
MouseManager
中的event对象。如果监听事件时,自己设置参数,则会在自定义参数数组后,添加event事件。
else if (args) result = method.apply(caller, args.concat(data));
1 MouseManager: 2 private function check(sp:Sprite, mouseX:Number, mouseY:Number, callBack:Function):Boolean { 3 this._point.setTo(mouseX, mouseY); 4 sp.fromParentPoint(this._point); 5 mouseX = this._point.x; 6 mouseY = this._point.y; 7 8 //如果有裁剪,则先判断是否在裁剪范围内 9 var scrollRect:Rectangle = sp.scrollRect; 10 if (scrollRect) { 11 _rect.setTo(scrollRect.x, scrollRect.y, scrollRect.width, scrollRect.height); 12 if (!_rect.contains(mouseX, mouseY)) return false; 13 } 14 15 //先判定子对象是否命中 16 if (!disableMouseEvent) { 17 //优先判断父对象 18 //默认情况下,hitTestPrior=mouseThrough=false,也就是优先check子对象 19 //$NEXTBIG:下个重大版本将sp.mouseThrough从此逻辑中去除,从而使得sp.mouseThrough只负责目标对象的穿透 20 if (sp.hitTestPrior && !sp.mouseThrough && !hitTest(sp, mouseX, mouseY)) { 21 return false; 22 } 23 for (var i:int = sp._childs.length - 1; i > -1; i--) { //倒叙遍历,从外向内检测,如果检测到则跳过内部检测 24 var child:Sprite = sp._childs[i]; 25 //只有接受交互事件的,才进行处理 26 if (!child.destroyed && child.mouseEnabled && child.visible) { 27 if (check(child, mouseX, mouseY, callBack)) return true; 28 } 29 } 30 } 31 32 //避免重复进行碰撞检测,考虑了判断条件的命中率。 33 var isHit:Boolean = (sp.hitTestPrior && !sp.mouseThrough && !disableMouseEvent) ? true : hitTest(sp, mouseX, mouseY); 34 35 if (isHit) { 36 _target = sp; 37 callBack.call(this, sp); 38 } else if (callBack === onMouseUp && sp === _stage) { 39 //如果stage外mouseUP 40 _target = _stage; 41 callBack.call(this, _target); 42 } 43 44 return isHit; 45 } 46 47 private function hitTest(sp:Sprite, mouseX:Number, mouseY:Number):Boolean { 48 var isHit:Boolean = false; 49 if (sp.scrollRect) { 50 mouseX -= sp.scrollRect.x; 51 mouseY -= sp.scrollRect.y; 52 } 53 if (sp.hitArea is HitArea) { 54 return sp.hitArea.isHit(mouseX, mouseY); 55 } 56 if (sp.width > 0 && sp.height > 0 || sp.mouseThrough || sp.hitArea) { 57 //判断是否在矩形区域内 58 if (!sp.mouseThrough) { 59 var hitRect:Rectangle = this._rect; 60 if (sp.hitArea) hitRect = sp.hitArea; 61 else hitRect.setTo(0, 0, sp.width, sp.height); //坐标已转换为本地坐标系 62 isHit = hitRect.contains(mouseX, mouseY); 63 } else { 64 //如果可穿透,则根据子对象实际大小进行碰撞 65 isHit = sp.getGraphicBounds().contains(mouseX, mouseY); 66 } 67 } 68 return isHit; 69 } 70 71 /** 72 * 执行事件处理。 73 */ 74 public function runEvent():void { 75 var len:int = _eventList.length; 76 if (!len) return; 77 78 var _this:MouseManager = this; 79 var i:int = 0,j:int,n:int,touch:*; 80 while (i < len) { 81 var evt:* = _eventList[i]; 82 83 if (evt.type !== 'mousemove') _prePoint.x = _prePoint.y = -1000000; 84 85 switch (evt.type) { 86 case 'mousedown': 87 _touchIDs[0] = _id++; 88 if (!_isTouchRespond) { 89 _this._isLeftMouse = evt.button === 0; 90 _this.initEvent(evt); 91 _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseDown); 92 } else 93 _isTouchRespond = false; 94 break; 95 case 'mouseup': 96 _this._isLeftMouse = evt.button === 0; 97 _this.initEvent(evt); 98 _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseUp); 99 break; 100 case 'mousemove': 101 if ((Math.abs(_prePoint.x - evt.clientX) + Math.abs(_prePoint.y - evt.clientY)) >= mouseMoveAccuracy) { 102 _prePoint.x = evt.clientX; 103 _prePoint.y = evt.clientY; 104 _this.initEvent(evt); 105 _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseMove); 106 // _this.checkMouseOut(); 107 } 108 break; 109 case "touchstart": 110 _isTouchRespond = true; 111 _this._isLeftMouse = true; 112 var touches:Array = evt.changedTouches; 113 for (j = 0, n = touches.length; j < n; j++) { 114 touch = touches[j]; 115 //是否禁用多点触控 116 if (multiTouchEnabled || isNaN(_curTouchID)) { 117 _curTouchID = touch.identifier; 118 //200次点击清理一下id资源 119 if (_id % 200 === 0) _touchIDs = {}; 120 _touchIDs[touch.identifier] = _id++; 121 _this.initEvent(touch, evt); 122 _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseDown); 123 } 124 } 125 126 break; 127 case "touchend": 128 case "touchcancel": 129 _isTouchRespond = true; 130 _this._isLeftMouse = true; 131 var touchends:Array = evt.changedTouches; 132 for (j = 0, n = touchends.length; j < n; j++) { 133 touch = touchends[j]; 134 //是否禁用多点触控 135 if (multiTouchEnabled || touch.identifier == _curTouchID) { 136 _curTouchID = NaN; 137 _this.initEvent(touch, evt); 138 var isChecked:Boolean; 139 isChecked = _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseUp); 140 if (!isChecked) 141 { 142 _this.onMouseUp(null); 143 } 144 } 145 } 146 147 break; 148 case "touchmove": 149 var touchemoves:Array = evt.changedTouches; 150 for (j = 0, n = touchemoves.length; j < n; j++) { 151 touch = touchemoves[j]; 152 //是否禁用多点触控 153 if (multiTouchEnabled || touch.identifier == _curTouchID) { 154 _this.initEvent(touch, evt); 155 _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseMove); 156 } 157 } 158 break; 159 case "wheel": 160 case "mousewheel": 161 case "DOMMouseScroll": 162 _this.checkMouseWheel(evt); 163 break; 164 case "mouseout": 165 //_this._stage.event(Event.MOUSE_OUT, _this._event.setTo(Event.MOUSE_OUT, _this._stage, _this._stage)); 166 TouchManager.I.stageMouseOut(); 167 break; 168 case "mouseover": 169 _this._stage.event(Event.MOUSE_OVER, _this._event.setTo(Event.MOUSE_OVER, _this._stage, _this._stage)); 170 break; 171 } 172 i++; 173 } 174 _eventList.length = 0; 175 } 176 } 177 TouchManager 178 /** 179 * 派发事件。 180 * @param eles 对象列表。 181 * @param type 事件类型。 182 * @param touchID (可选)touchID,默认为0。 183 */ 184 private function sendEvents(eles:Array, type:String, touchID:int = 0):void { 185 var i:int, len:int; 186 len = eles.length; 187 _event._stoped = false; 188 var _target:*; 189 _target = eles[0]; 190 var tE:Sprite; 191 for (i = 0; i < len; i++) { 192 tE = eles[i]; 193 if (tE.destroyed) return; 194 tE.event(type, _event.setTo(type, tE, _target)); 195 if (_event._stoped) 196 break; 197 } 198 } 199 200 /** 201 * 获取对象列表。 202 * @param start 起始节点。 203 * @param end 结束节点。 204 * @param rst 返回值。如果此值不为空,则将其赋值为计算结果,从而避免创建新数组;如果此值为空,则创建新数组返回。 205 * @return Array 返回节点列表。 206 */ 207 private function getEles(start:Node, end:Node = null, rst:Array = null):Array { 208 if (!rst) { 209 rst = []; 210 } else { 211 rst.length = 0; 212 } 213 while (start && start != end) { 214 rst.push(start); 215 start = start.parent; 216 } 217 return rst; 218 }
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。