本文由泰然翻译组组长 TXX_糖炒小虾 原创,版权全部,转载请注明出处并通知做者和泰然!api
原做 http://www.ityran.com/archives/1326/comment-page-1数组
触摸是iOS程序的精髓所在,良好的触摸体验能让iOS程序获得很是好的效果,例如Clear。
鉴于同窗们只会用cocos2d的 CCTouchDispatcher 的 api 但并不知道工做原理,但了解触摸分发的过程是极为重要的。毕竟涉及到权限、两套协议等的各类分发。因而我写了这篇文章来抛砖引玉。缓存
本文以cocos2d-iphone源代码为讲解。cocos2d-x 于此相似,就不过多赘述了。app
零、cocoaTouch的触摸
在讲解cocos2d触摸协议以前,我以为我有必要提一下CocoaTouch那四个方法。毕竟cocos2d的Touch Delegate 也是经过这里接入的。iphone
一、一个UITouch的生命周期
一个触摸点会被包装在一个UITouch中,在TouchesBegan的时候建立,在Cancelled或者Ended的时候被销毁。也就是说,一个触摸点在这四个方法中内存地址是相同的,是同一个对象。
二、UIEvent
这是一个常常被大伙儿忽视的东西,基本上没见过有谁用过,不过这个东西的确不经常使用。能够理解为UIEvent是UITouch的一个容器。
你能够经过UIEvent的allTouches方法来得到当前全部触摸事件。那么和传入的那个NSSet有什么区别呢?
那么来设想一个状况,在开启多点支持的状况下,我有一个手指按在屏幕上,既不移动也不离开。而后,又有一只手指按下去。
这时TouchBegan会被触发,它接到的NSSet的Count为1,仅有一个触摸点。
可是UIEvent的alltouches 倒是2,也就是说那个按在屏幕上的手指的触摸信息,是能够经过此方法获取到的,并且他的状态是UITouchPhaseStationary
三、关于Cancelled的误区
有不少人认为,手指移出屏幕、或移出那个View的Frame 会触发touchCancelled,这是个很大的误区。移出屏幕触发的是touchEned,移出view的Frame不会致使触摸终止,依然是Moved状态。
那么Cancelled是干什么用的?
官方解释:This method is invoked when the Cocoa Touch framework receives a system interruption requiring cancellation of the touch event; for this, it generates a UITouch object with a phase of UITouchPhaseCancel. The interruption is something that might cause the application to be no longer active or the view to be removed from the window
当Cocoa Touch framework 接到系统中断通知须要取消触摸事件的时候会调用此方法。同时会将致使一个UITouch对象的phase改成UITouchPhaseCancel。这个中断每每是由于app长时间没有响应或者当前view从window上移除了。异步
据我统计,有这么几种状况会致使触发Cancelled:
一、官方所说长时间无响应,view被移除
二、触摸的时候来电话,弹出UIAlert View(低电量 短信 推送 之类),按了home键。也就是说程序进入后台。
三、屏幕关闭,触摸的时候,某种缘由致使距离传感器工做,例如脸靠近。
四、手势的权限盖掉了Touch, UIGestureRecognizer 有一个属性:函数
关于CocoaTouch就说到这里,CocoaTouch的Touch和Gesture混用 我会在未来的教程中写明。
1、TouchDelegate的接入。优化
众所周知CCTouchDelegate是经过CocoaTouch的API接入的,那么是从哪里接入的呢?咱们是知道cocos2d是跑在一个view上的,这个view 就是 EAGLView 可在cocos2d的Platforms的iOS文件夹中找到。
在它的最下方能够看到,他将上述四个api传入了一个delegate。这个delegate是谁呢?
没错就是CCTouchDispatcherui
但纵览整个EAGLView的.m文件,你是找不到任何和CCTouchDispatcher有关的东西的。
那么也就是说在初始化的时候载入的咯?this
EAGLView的初始化在appDelegate中,但依然没看到有关CCTouchDispatcher 有关的东西,但能够留意一句话:
点开后能够发现
呵呵~ CCTouchDispatcher 被发现了!
2、两套协议
CCTouchDispatcher 提供了两套协议。
与之对应的还有两个在CCTouchDispatcher 中的添加操做
其中StandardTouchDelegate 单独使用的时候用法和 cocoaTouch 相同。
咱们这里重点说一下CCTargetedTouchDelegate
在头文件的注释中能够看到:
使用它的好处:
一、不用去处理NSSet, 分发器会将它拆开,每次调用你都能精确的拿到一个UITouch
二、你能够在touchbegan的时候retun yes,这样以后touch update 的时候 再得到到的touch 确定是它本身的。这样减轻了你对多点触控时的判断。
除此以外还有
三、TargetedTouchDelegate支持SwallowTouch 顾名思义,若是这个开关打开的话,比他权限低的handler 是收不到 触摸响应的,顺带一提,CCMenu 就是开了Swallow 而且权限为-128(权限是越小越好)
四、 CCTargetedTouchDelegate 的级别比 CCStandardDelegate 高,高在哪里了呢? 在后文讲分发原理的时候 我会说具体说明。
3、CCTouchHandler
在说分发以前,还要介绍下这个类的做用。
简而言之呢,这个类就是用于存储你的向分发器注册协议时的参数们。
类指针,类所拥有的那几个函数们,以及触摸权限。
只不过在 CCTargetedTouchHandler 中还有这么一个东西
这个东西就是记录当前这个delegate中 拿到了多少 Touches 罢了。
只是想在这里说一点:
UITouch只要手指按在屏幕上 不管是滑动 也好 开始began 也好 finished 也好
对于一次touch操做,从开始到结束 touch的指针是不变的.
4、触摸分发
前面铺垫这么多,终于讲到重点了。
这里我就结合这他的代码说好了。
首先先说dispatcher定义的数据成员
开始那两个 数组 顾名思义是存handlers的 不用多说
以后下面那一段的东西是用于线程间数据修改时的标记。
提一下那个lock为真的时候 表明当前正在进行触摸分发
而后是总开关
最后就是个helper 。。
而后说以前提到过的那两个插入方法
就是按照priority插入对应的数组中。
但要注意一点:当前若正在进行事件分发,是不进行插入的。取而代之的是放到一个缓存数组中。等触摸分发结束后才加入其中。
在讲分发前,再提一个函数
调整权限,讲它的目的是为了讲它中间包含的两个方法一个c函数,
调整权限的过程就是,先找到那个handler的指针,修改它的数值,而后对两个数组从新排序。 这里有几个细节: 一、findHandler 是先找 targeted 再找standard 且找到了就 return。也就是说 若是 一个类既注册了targeted又注册了standard,这里会出现冲突。 二、排序的比较器函数 只比较权限,其余一概不考虑。 在dispatcher.m的文件中末,能够看到EAGLTouchDelegate 全都指向了
这个方法。
他就是整个 dispatcher的核心。
下面咱们来分段讲解下。
最开始
首先开启了锁,以后是一个小优化。
就是说 若是 target 和 standard 这两个数组中 有一个为空的话 就不用 将传入的 set copy 一遍了。
下面开始正题
targeted delegate 分发!
其实分发很简单,先枚举每一个触摸点,而后枚举targeted数组中的handler
若当前触摸是 began 的话 那么就 运行 touchbegan函数 若是 touch began return Yes了 那么证实这个触摸被claim了。加入handler的那个集合中。
若当前触摸不是began 那么判断 handler那个集合中有没有这个 UItouch 若是有 证实 以前的touch began return 了Yes 能够继续update touch。 若操做是结束或者取消,就从set中把touch删掉。
最后这点很重要 当前handler是claim且设置为吞掉触摸的话,会删除standardtouchdelegate中对应的触摸点,而且终止循环。
targeted全部触摸事件分发完后开始进行standard 触摸事件分发。
按这个次序咱们能够发现…
一、再次提起swallow,一旦targeted设置为swallow 比它权限低的 以及 standard 不管是多高的权限 全都收不到触摸分发。
二、standard的触摸权限 设置为 负无穷(最高) 也没有 targeted的正无穷(最低)权限高。
三、触摸分发,只和权限有关,和层的高度(zOrder)彻底不要紧,哪怕是一样的权限,也有可能低下一层先收到触摸,上面那层才接到。权限相同时数组里是乱序的,非插入顺序。
最后,关闭锁
开始判断在数据分发的时候有没有发生 添加 删除 清空handler的状况。
结束分发
注意,事件分发后的异步处理信息会出现几个有意思的反作用
一、删除的时候 retainCnt +1由于要把handler暂时加入缓存数组中。
虽然说是暂时的,可是会混淆你的调试。
例如:
若是你内存管理作得好的话,应该是 输出 2 和 3
2 是在 addchild 和 dispatcher中添加了。
3 是在 cache 中又被添加一次。
二、有些操做会失去你想要表达的效果。
例如一个你写了个ScrollView 上面有一大块menu。你想在手指拖拽view的时候 屏蔽掉 那个menu的响应。
也许你会这么作:
1)让scrollview的权限比menu还要高,并设为不吞掉触摸。
2)滑动的时候,scrollview确定会先收到触摸,这时取消掉menu的响应。
3)触摸结束还,还原menu响应
但实际上第二步的时候 menu 仍是会收到响应的,会把menu的item变成selected状态。而且须要手动还原
样例代码以下:
三、须要注意的一点是,TouchTargetedDelegate 并无屏蔽掉多点触摸,而是将多点离散成了单点,同时传递过来了。
也就是说,每个触摸点都会走UITouch LifeCircle ,只是由于在正常状况下NSSet提取出来的信息顺序相同,使得你每次操做看起来只是最后一个触摸点生效了。可是若是用户“手贱”,多指触摸,并不一样时抬起所有手指,你将收到诸如start(-move)-end-(move)-end 之类的状况。若开启了多点触控支持,必定要考虑好这点!不然可能会被用户玩出来一些奇怪的bug…