Laya鼠标事件阅读

  1. 点击事件核心类:MouseManagerTouchManager
    MouseManager负责收集相关事件,进行捕获阶段和目标阶段。
    TouchManger负责处理和分发事件,进行冒泡阶段。
    • 捕获阶段:此阶段引擎会从stage开始递归检测stage及其子对象,直到找到命中的目标对象或者未命中任何对象;
    • 目标阶段:找到命中的目标对象;
    • 冒泡阶段:事件离开目标对象,按节点层级向上逐层通知,直到到达舞台的过程。</p>
  2. 事件是由Canvas(浏览器控件等)发起,在MouseManager中注册处理。
  3. MouseManager在监听到事件后,会将事件添加到队列中,在stage进入下一帧时(Stage._loop),一次性处理完所有事件。
  4. 捕获、目标阶段核心逻辑:(从根节点开始,向子节点查找)
    1. 初始化Event。MouseManager维护一个唯一的Event对象,保留鼠标事件相关信息,如target、touchid等。
    2. 先判断sp是否有scrollRect,如果则scrollRect外,直接判定为没点到,返回false。
    3. 优先检测(HitTestPrior),并且没有点击到,则直接返回false。(width>0并且没有点击穿透的View,默认会被设置为HitTestPrior = true
    4. 命中检测逻辑(hitTest
      1. 参数为被判断的sp和转换为相对与sp的鼠标坐标(通过fromParentPoint方法)
      2. 如果有scrollRect,则先偏移鼠标点击位置。
      3. 如果sp的hitArea字段不为空,则返回sp的hitArea.isHit结果。
      4. 如果mouseThrough为true,则检测子对象的实际大小进行碰撞。否则就使用(0,0,width,height)的矩形检测点是否在矩形内。
    5. 倒叙遍历子对象,从外向内检测,检测到后,直接跳过内部检测。
    6. 将目标对象和Event对象传递给TouchManager
  5. 冒泡阶段核心逻辑:(从子节点开始,向根节点查找)
    1. 获取或创建的点击信息,用于检测拖拽、双击等。
    2. 从当前sp开始,递归收集父节点,按顺序放入数组中,子节点在前,父节点在后。
    3. 按照数组属性,对节点发送相关事件,如果事件被阻断(event.stopPropagation),则直接跳过所有父节点。事件的currentTarget为最先被点击的sp。
  6. 鼠标事件触发后,参数默认为MouseManager中的event对象。如果监听事件时,自己设置参数,则会在自定义参数数组后,添加event事件。
    else if (args) result = method.apply(caller, args.concat(data));
MouseManager:
private function check(sp:Sprite, mouseX:Number, mouseY:Number, callBack:Function):Boolean {
   this._point.setTo(mouseX, mouseY);
   sp.fromParentPoint(this._point);
   mouseX = this._point.x;
   mouseY = this._point.y;
   
   //如果有裁剪,则先判断是否在裁剪范围内
   var scrollRect:Rectangle = sp.scrollRect;
   if (scrollRect) {
    _rect.setTo(scrollRect.x, scrollRect.y, scrollRect.width, scrollRect.height);
    if (!_rect.contains(mouseX, mouseY)) return false;
   }
   
   //先判定子对象是否命中
   if (!disableMouseEvent) {
    //优先判断父对象
    //默认情况下,hitTestPrior=mouseThrough=false,也就是优先check子对象
    //$NEXTBIG:下个重大版本将sp.mouseThrough从此逻辑中去除,从而使得sp.mouseThrough只负责目标对象的穿透
    if (sp.hitTestPrior && !sp.mouseThrough && !hitTest(sp, mouseX, mouseY)) {
     return false;
    }
    for (var i:int = sp._childs.length - 1; i > -1; i--) {  //倒叙遍历,从外向内检测,如果检测到则跳过内部检测
     var child:Sprite = sp._childs[i];
     //只有接受交互事件的,才进行处理
     if (!child.destroyed && child.mouseEnabled && child.visible) {
      if (check(child, mouseX, mouseY, callBack)) return true;
     }
    }
   }
   
   //避免重复进行碰撞检测,考虑了判断条件的命中率。
   var isHit:Boolean = (sp.hitTestPrior && !sp.mouseThrough && !disableMouseEvent) ? true : hitTest(sp, mouseX, mouseY);
   
   if (isHit) {
    _target = sp;
    callBack.call(this, sp);
   } else if (callBack === onMouseUp && sp === _stage) {
    //如果stage外mouseUP
    _target = _stage;
    callBack.call(this, _target);
   }
   
   return isHit;
  }
  
  private function hitTest(sp:Sprite, mouseX:Number, mouseY:Number):Boolean {
   var isHit:Boolean = false;
   if (sp.scrollRect) {
    mouseX -= sp.scrollRect.x;
    mouseY -= sp.scrollRect.y;
   }
   if (sp.hitArea is HitArea) {
    return sp.hitArea.isHit(mouseX, mouseY);
   }
   if (sp.width > 0 && sp.height > 0 || sp.mouseThrough || sp.hitArea) {
    //判断是否在矩形区域内
    if (!sp.mouseThrough) {
     var hitRect:Rectangle = this._rect;
     if (sp.hitArea) hitRect = sp.hitArea;
     else hitRect.setTo(0, 0, sp.width, sp.height); //坐标已转换为本地坐标系
     isHit = hitRect.contains(mouseX, mouseY);
    } else {
     //如果可穿透,则根据子对象实际大小进行碰撞
     isHit = sp.getGraphicBounds().contains(mouseX, mouseY);
    }
   }
   return isHit;
  }
  
  /**
   * 执行事件处理。
   */
  public function runEvent():void {
   var len:int = _eventList.length;
   if (!len) return;
   
   var _this:MouseManager = this;
   var i:int = 0,j:int,n:int,touch:*;
   while (i < len) {
    var evt:* = _eventList[i];
    
    if (evt.type !== 'mousemove') _prePoint.x = _prePoint.y = -1000000;
    
    switch (evt.type) {
    case 'mousedown': 
     _touchIDs[0] = _id++;
     if (!_isTouchRespond) {
      _this._isLeftMouse = evt.button === 0;
      _this.initEvent(evt);
      _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseDown);
     } else
      _isTouchRespond = false;
     break;
    case 'mouseup': 
     _this._isLeftMouse = evt.button === 0;
     _this.initEvent(evt);
     _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseUp);
     break;
    case 'mousemove': 
     if ((Math.abs(_prePoint.x - evt.clientX) + Math.abs(_prePoint.y - evt.clientY)) >= mouseMoveAccuracy) {
      _prePoint.x = evt.clientX;
      _prePoint.y = evt.clientY;
      _this.initEvent(evt);
      _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseMove);
       //   _this.checkMouseOut();
     }
     break;
    case "touchstart": 
     _isTouchRespond = true;
     _this._isLeftMouse = true;
     var touches:Array = evt.changedTouches;
     for (j = 0, n = touches.length; j < n; j++) {
      touch = touches[j];
      //是否禁用多点触控
      if (multiTouchEnabled || isNaN(_curTouchID)) {
       _curTouchID = touch.identifier;
       //200次点击清理一下id资源
       if (_id % 200 === 0) _touchIDs = {};
       _touchIDs[touch.identifier] = _id++;
       _this.initEvent(touch, evt);
       _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseDown);
      }
     }
     
     break;
    case "touchend": 
    case "touchcancel":
     _isTouchRespond = true;
     _this._isLeftMouse = true;
     var touchends:Array = evt.changedTouches;
     for (j = 0, n = touchends.length; j < n; j++) {
      touch = touchends[j];
      //是否禁用多点触控
      if (multiTouchEnabled || touch.identifier == _curTouchID) {
       _curTouchID = NaN;
       _this.initEvent(touch, evt);
       var isChecked:Boolean;
       isChecked = _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseUp);
       if (!isChecked)
       {
        _this.onMouseUp(null);
       }
      }
     }
     
     break;
    case "touchmove": 
     var touchemoves:Array = evt.changedTouches;
     for (j = 0, n = touchemoves.length; j < n; j++) {
      touch = touchemoves[j];
      //是否禁用多点触控
      if (multiTouchEnabled || touch.identifier == _curTouchID) {
       _this.initEvent(touch, evt);
       _this.check(_this._stage, _this.mouseX, _this.mouseY, _this.onMouseMove);
      }
     }
     break;
    case "wheel": 
    case "mousewheel": 
    case "DOMMouseScroll": 
     _this.checkMouseWheel(evt);
     break;
    case "mouseout": 
     //_this._stage.event(Event.MOUSE_OUT, _this._event.setTo(Event.MOUSE_OUT, _this._stage, _this._stage));
     TouchManager.I.stageMouseOut();
     break;
    case "mouseover": 
     _this._stage.event(Event.MOUSE_OVER, _this._event.setTo(Event.MOUSE_OVER, _this._stage, _this._stage));
     break;
    }
    i++;
   }
   _eventList.length = 0;
  }
 }
TouchManager
/**
   * 派发事件。
   * @param eles    对象列表。
   * @param type    事件类型。
   * @param touchID (可选)touchID,默认为0。
   */
  private function sendEvents(eles:Array, type:String, touchID:int = 0):void {
   var i:int, len:int;
   len = eles.length;
   _event._stoped = false;
   var _target:*;
   _target = eles[0];
   var tE:Sprite;
   for (i = 0; i < len; i++) {
    tE = eles[i];
    if (tE.destroyed) return;
    tE.event(type, _event.setTo(type, tE, _target));
    if (_event._stoped)
     break;
   }
  }
  
  /**
   * 获取对象列表。
   * @param start   起始节点。
   * @param end 结束节点。
   * @param rst 返回值。如果此值不为空,则将其赋值为计算结果,从而避免创建新数组;如果此值为空,则创建新数组返回。
   * @return Array 返回节点列表。
   */
  private function getEles(start:Node, end:Node = null, rst:Array = null):Array {
   if (!rst) {
    rst = [];
   } else {
    rst.length = 0;
   }
   while (start && start != end) {
    rst.push(start);
    start = start.parent;
   }
   return rst;
  }
  
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,470评论 1 11
  • 事件是什么,可以用来做什么,什么时候用到它? 事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。JavaScr...
    茂茂爱吃鱼阅读 1,502评论 0 16
  • 第13章 事件 1. 事件流 事件流描述的是从页面中接收事件的顺序。 (1) 事件冒泡 IE 的事件流叫做事件冒泡...
    yinxmm阅读 934评论 0 17
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,096评论 0 21
  • 以下文章为转载,对理解JavaScript中的事件处理机制很有帮助,浅显易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy阅读 3,022评论 1 10