本文由泰然翻譯組組長 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…