触摸事件冒泡插图

这两天正在愁公众号写点什么,打开微信看到uikiller用户「悦雨」遇到了一个问题:

地图拖动与子节点触摸事件产生冲突,表现为在子节点上拖动,地图不能动

一句话不太好描述问题,在征得「悦雨」同意后,将这次交流的内容截图出来:

第一话

问题描述
触摸事件冒泡插图

第二话

ScrollView解决方案
触摸事件冒泡插图(1)

在与「悦雨」的交流过程中,我用ScrollView+TileMap+Button+AudioSource花了五分钟做了一个小测试,将TiledMap放在ScrollView中,在TiledMap中又放值了一个按钮,验证了一下曾经的经验是否任然有效,结果是OK的,于是将测试场景发给了「悦雨」。

第三话

结果是OK的,于是将测试场景发给了「悦雨」同学,但ScrollView不是想要的,继续聊这个问题:
触摸事件冒泡插图(2)

第四话

不想用ScrollView,还有什么方案呢?触摸事件捕获!继续对话:
触摸事件冒泡插图(3)

快速原型

有了这个案例今天就以这个地图场景为例,看看不写代码,利用引擎内置组件,如何快速实现一个原型或组件功能测试 ,请看下面视频:

从源码中学习

为什么在ScrollView中拖动,不会触发子节点的TOUCH事件呢?先看下ScrollView上的一个关键属性:
ScrollView.png

有了Cancel Inner Event这个线索,我们直接从ScrollView组件源码入手,看看它是什么实现的。

以cc.ScrollView组件为例,看如何定位组件源码:
1. 使用Chrome浏览器启动游戏预览
2. 打开Chrome DevTools工具
3. 键盘快捷键:ctrl + p 或 cmd + p
4. 输入:ccscrollview (引擎组件原文件名公式:cc + 组件名)
5. 从显示的列表上找到要查看的源码文件

触摸事件冒泡插图(5)

选择CCScrollView.js文件,自动跳转到Sources标签,打开文件内容,键入ctrl + f 或 cmd + f 在当前文件中搜索:cancelInnerEvents,找到关键代码:

ScrollView源码.png

  1. 可以看到976行中,当this.cancelInnerEvents变量为真可能会执行到下面的代码,设置成员变量this._touchMoved = true
  2. 再看1006行_onTouchEnd函数,在这里判断了_touchMoved这个变量,停止TOUCH_END事件的传播,这样子节点的触摸事件就不会被触发了
  3. 993行_onTouchMoved函数最后一行代码this._stopPropagationIfTargetIsMe(event)它是在有条件地停止TOUCH_MOVE事件的传播。

通过上面的分析,再通过断点跟踪,在ScrollView和Button组件中分别打上断点,我们在Button组件上做点击,ScrollView组件的_onTouchEnded居然先被断下来,它是怎么做到的呢?

在CCScrollView.js源码中搜“TOUCH_END”关键字,找到TOUCH事件注册的代码:

image.png

看看这里有与自己平时注册TOUCH事件有什么不同?相信你已经发现了,关键在最后一个参数:useCapture,用于是否捕获子节点事件,又称之为向下冒泡(默认是向上冒泡),下面以TOUC_END事件为例,简单说明一下:

this.node.on(
  cc.Node.EventType.TOUCH_END,   //触摸事件类型
  this._onTouchEnded,            //事件处理函数
  this,                          //事件处理函数的this上下文(使用箭头函数时通常被省略)
  true                           //是否捕获子节点Touch事件
);

为了帮助大家更好地理解,我做了个简单的小组件,请看代码:

cc.Class({
    extends: cc.Component,
    properties: {
        useCapture: false, //是否启用捕获
    },
    onLoad () {
        this.node.on(
            cc.Node.EventType.TOUCH_END,
            () => cc.log('touchend', this.node.name), //测试时观察日志输入出
            this, 
            this.useCapture
        );
    }
});

把这个组件挂到两个父子关系的节点上,在父节点上开启捕获,看下面截图:
触摸事件冒泡插图(8)

运行点击红色节点,看看日志输出:
触摸事件冒泡插图(9)
从日志中看到白色节点先响应,然后是红色节点,我们把白色父节点的UseCapture关闭,再看看日志输出:

触摸事件冒泡插图(10)
这次是红色子节点先响应,白色父节点后响应,更多细节可以参考Cocos Creator官方文档:https://docs.cocos.com/creator/manual/zh/scripting/internal-events.html?h=%E5%86%92%E6%B3%A1

还有对应的官方范例:TouchPropagation

触摸事件冒泡插图(11)

题外话

这次除了教程,还想再聊一个事情,经常会有同学通过微信、QQ、公众号向Shawn咨询问题,首先感谢大家对shawn的信任,如果是在自己的能力范围内且对大家帮助的内容,Shawn一定真诚对待,这也正是「奎特尔星球」内容的重要来源。但是因为个人能力和时间有限,不是每一个人的问题Shawn都能解答,还望大家见谅。

微信、QQ很容易让人在工作时分心,一般我在做事的时候会将手机静音或离远一点,公众号上偶尔也收有留言,但有时会忘记去公众号上查看,超过24小时的留言,看到了想回复也无没办法,很是无奈。为了能把公众号做好,Shawn特地定制了一个邮箱:shawn@creator-star.cn,如有问题讨论或投稿欢邮件联系,公众号需要大家的支持和帮助,愿我们一起成长!

最后修改日期:2018年12月11日

作者