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