1. 点击事件核心类:MouseManagerTouchManager
    MouseManager负责收集相关事件,进行捕获阶段和目标阶段。
    TouchManger负责处理和分发事件,进行冒泡阶段。

    • 捕获阶段:此阶段引擎会从stage开始递归检测stage及其子对象,直到找到命中的目标对象或者未命中任何对象;
    • 目标阶段:找到命中的目标对象;
    • 冒泡阶段:事件离开目标对象,按节点层级向上逐层通知,直到到达舞台的过程。
  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;
}
 
 
 
 

Laya鼠标事件阅读的更多相关文章

  1. DuiLib事件分析(一)——鼠标事件响应

    最近在处理DuiLib中自定义列表行元素事件,因为处理方案得不到较好的效果,于是只好一层一层的去剥离DuiLib事件是怎么来的,看能否在某一层截取消息,自己重写. 我这里使用CListContaine ...

  2. Python游戏引擎开发(五):Sprite精灵类和鼠标事件

    本次来实现Sprite类和鼠标事件. 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它. 它中文原意是"精灵",只是在不同人的眼中,它所表示的意义不同. 比方说在 ...

  3. win10 支持默认把触摸提升鼠标事件 打开 Pointer 消息

    原文:win10 支持默认把触摸提升鼠标事件 打开 Pointer 消息 在 WPF 经常需要重写一套触摸事件,没有UWP的Pointer那么好用. 如果一直都觉得 WPF 的触摸做的不好,或想解决 ...

  4. 7.JAVA之GUI编程鼠标事件

    鼠标事件: 功能: 1.基本窗体功能实现 2.鼠标移动监听,当鼠标移动到按钮上时,触发打印事件. 3.按钮活动监听,当按钮活动时,触发打印事件. 4.按钮被单击时触发打印事件. 源码如下: impor ...

  5. 手持设备点击响应速度,鼠标事件与touch事件的那些事

    前言 现在一直在做移动端的开发,这次将单页应用的网页内嵌入了app,于是老大反映了一个问题:app应用点击响应慢!我开始不以为然,于是拿着网页版的试了试,好像确实有一定延迟,于是开始了研究,最后选择了 ...

  6. css屏蔽元素的鼠标事件pointer-events

    // 屏蔽点击 $('body').css('pointer-events', 'none'); //恢复默认 $('body').css('pointer-events', 'auto');   用 ...

  7. 深入学习jQuery鼠标事件

    × 目录 [1]类型 [2]写法 [3]合成事件[4]鼠标按键[5]修改键[6]坐标位置 前面的话 鼠标事件是DOM事件中最常用的事件,jQuery对鼠标事件进行了封装和扩展.本文将详细介绍jQuer ...

  8. 深入理解DOM事件类型系列第一篇——鼠标事件

    × 目录 [1]类型 [2]顺序 [3]坐标位置[4]修改键[5]相关元素[6]鼠标按键[7]滚轮事件[8]移动设备 前面的话 鼠标事件是web开发中最常用的一类事件,毕竟鼠标是最主要的定位设备.本文 ...

  9. winform/窗体鼠标事件编程中的几个问题

    1.进行.net窗体的开发,经常用到鼠标事件,如MouseDown/MouseUp/MouseMove/MouseClick等.可是有时候给控件添加鼠标事件,就是不响应,怎么办呢! 答案:1.控件是否 ...

随机推荐

  1. Regmap 框架:简化慢速IO接口优化性能【转】

    1. 简介 Regmap 机制是在 Linux 3.1 加入进来的特性.主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器.其实这就是内核做的一次重构.Regma ...

  2. python基础学习1

    一.python第一个程序 print("hello world") 二.变量的命名规则 1. 字母数字下划线组成 2. 不能以数字开头,不能含有特殊字符和空格 3. 不能以保留字 ...

  3. mysql 大文件导入导出

    导出:mysqldump -u用户名 -p密码 -hIP地址 数据库名 > /dump.sql示例:mysqldump -uroot -proot -h127.0.0.1 test > / ...

  4. TiDB数据库集群安装以及注意事项

    今天尝试安装tidb集群.详细的安装步骤我们参考:https://pingcap.com/docs-cn/op-guide/ansible-deployment/ . 不过安装之前需要一些注意事项. ...

  5. HTML5 学习总结(三)——本地存储(localStorage、sessionStorage、WebSqlDataBase、IndexedDB)

    HTML5问世以后,前端加入了一个重要的功能,便是本地存储,本地存储可分为4类: Local Storage:总的存储量有所限制,并不能提供真正的检索API,数据的生命期比窗口或浏览器的生命期长,数据 ...

  6. MySQL5.7通过压缩包方式安装与配置

    首先下载MySQL5.7的压缩包:https://dev.mysql.com/downloads/mysql/5.7.html#downloads 1.解压缩到目标文件夹,解压后有许多文件,介绍一下用 ...

  7. intellij IDEA软件java项目No SDK配置jdk开发,安装IDEA软件步骤

    我们在使用intellij idea开发java项目的时候,我们在创建的时候会发现提示No SDK,影响创建和使用项目,我们需要下载和配置需要的JDK 电脑 1我们使用intellij idea创建j ...

  8. pyspider爬取数据存入redis--1.安装驱动

    首先安装pyredis的驱动 wget https://pypi.python.org/packages/source/r/redis/redis-2.9.1.tar.gz 解压并cd python  ...

  9. jmeter接口测试4-使用数据库mysql构造参数

    jmeter测试中,测试数据一般和测试用例分离 测试数据一般可以使用csv构造,进行参数化 但也可以使用mysql等数据库构造 方案一:一个线程循环调用mysql数据,不是并发,不适用于性能测试,更适 ...

  10. clock gating check

    在 sta 分析时,经常会碰到 clock gating cell (一般是 ICG cell 或者 latch)引起的 violation,这种 violation 很常见,而且往往很难修. 为什么 ...