本文ios部分轉載自:html
http://zhoon.github.io/ios/2015/04/12/ios-event.htmlios
iOS的事件有好幾種:Touch Events(觸摸事件)、Motion Events(運動事件,好比重力感應和搖一搖等)、Remote Events(遠程事件,好比用耳機上得按鍵來控制手機),其中最經常使用的應該就是Touch Events了,基本存在於每一個app的每一個地方,今天咱們主要就講講它,至於其餘兩個事件有興趣的能夠自行查閱資料。git
在網頁上當咱們講到事件,咱們會講到事件響應鏈,咱們會講到事件的響應者和事件的傳遞方式(冒泡),那麼在app上,其實也離不開這幾個問題,今天咱們也重這幾個方面來介紹iOS的事件機制: 一、響應鏈是何時怎樣構建的? 二、事件第一個響應者是怎麼肯定的? 三、事件第一個響應者肯定後,系統是怎樣傳遞事件的?github
不管是哪一種事件,其傳遞和響應都與響應鏈息息相關,那麼響應鏈究竟是一個什麼樣的東西呢? 在UIKit中有一個類:UIResponder,咱們能夠看看頭文件的幾個屬性和方法:app
UIResponder是全部能夠響應事件的類的基類(從名字應該就能夠看出來了),其中包括最多見的UIView和UIViewController甚至是UIApplication,因此咱們的UIView和UIViewController都是做爲響應事件的載體。ide
那麼響應鏈跟這個UIResponder有什麼關係呢?事實事件響應鏈的造成和事件的響應和傳遞,UIResponder都幫咱們作了不少事。咱們的app中,全部的視圖都是按照必定的結構組織起來的,即樹狀層次結構,每一個view都有本身的superView,包括controller的topmost view(controller的self.view)。當一個view被add到superView上的時候,他的nextResponder屬性就會被指向它的superView,當controller被初始化的時候,self.view(topmost view)的nextResponder會被指向所在的controller,而controller的nextResponder會被指向self.view的superView,這樣,整個app就經過nextResponder串成了一條鏈,也就是咱們所說的響應鏈。因此響應鏈就是一條虛擬的鏈,並無一個對象來專門存儲這樣的一條鏈,而是經過UIResponder的屬性串連起來的。以下圖:函數
文章開頭說到有iOS三種event類型,事件傳遞中UIWindow會根據不一樣的event,用不一樣的方式尋找initial object,initial object決定於當前的事件類型。好比Touch Event,UIWindow會首先試着把事件傳遞給事件發生的那個view,就是下文要說的hit-testview。對於Motion和Remote Event,UIWindow會把例如震動或者遠程控制的事件傳遞給當前的firstResponder,有關firstResponder的相關信息請看這裏。下面主要講Touch Event的hit-testview。spa
有了事件響應鏈,接下來的事情就是尋找響應事件的具體響應者了,咱們稱着爲:Hit-Testing View,尋找這個View的過程咱們稱着爲Hit-Test。code
那麼什麼是Hit-Test呢,咱們能夠把它理解爲一個探測器,經過這個探測器咱們能夠找到並判斷手指是否點擊在某個視圖上面,換句話說就是經過Hit-Test能夠找到手指點擊到的處於屏幕最前面的那個UIView。htm
在解釋Hit-Test是怎麼工做以前,先來看看它是何時被調用的。前面說Hit-Test是一個探測器,那麼在代碼裏面其實就是一個函數,UIView有以下兩個方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
每當手指接觸屏幕,UIApplication接收到手指的事件以後,就會去調用UIWindow的hitTest:withEvent:,看看當前點擊的點是否是在window內,若是是則繼續依次調用subView的hitTest:withEvent:方法,直到找到最後須要的view。調用結束而且hit-test view肯定以後,這個view和view上面依附的手勢,都會和一個UITouch的對象關聯起來,這個UITouch會做爲事件傳遞的參數之一,咱們能夠看到UITouch頭文件裏面有一個view和gestureRecognizers的屬性,就是hitTest view和它的手勢。
如今知道Hit-Test是何時調用了,那麼接下來看看它是怎麼工做的。Hit-Test是採用遞歸的方法從view層級的根節點開始遍歷,看看下面這張圖:
UIWindow有一個MianVIew,MainView裏面有三個subView:view A、view B、view C,他們各自有兩個subView,他們層級關係是:view A在最下面,view B中間,view C最上(也就是addSubview的順序,越晚add進去越在上面),其中view A和view B有一部分重疊。若是手指在view B.1和view A.2重疊的上面點擊,按照上面說的遞歸方式,順序以下圖所示:
遞歸是向界面的根節點UIWindow發送hitTest:withEvent:消息開始的,從這個消息返回的是一個UIView,也就是手指當前位置最前面的那個 hittest view。 當向UIWindow發送hitTest:withEvent:消息時,hitTest:withEvent:裏面所作的事,就是判斷當前的點擊位置是否在window裏面,若是在則遍歷window的subview而後依次對subview發送hitTest:withEvent:消息(注意這裏給subview發送消息是根據當前subview的index順序,index越大就越先被訪問)。若是當前的point沒有在view上面,那麼這個view的subview也就不會被遍歷了。當事件遍歷到了view B.1,發現point在view B.1裏面,而且view B.1沒有subview,那麼他就是咱們要找的hittest view了,找到以後就會一路返回直到根節點,而view B以後的view A也不會被遍歷了。
一圖勝千言:
注意hitTest裏面是有判斷當前的view是否支持點擊事件,好比userInteractionEnabled、hidden、alpha等屬性,都會影響一個view是否能夠相應事件,若是不響應則直接返回nil。 咱們留意到還有一個pointInside:withEvent:方法,這個方法跟hittest:withEvent:同樣都是UIView的一個方法,經過他開判斷point是否在view的frame範圍內。若是這些條件都知足了,那麼遍歷就能夠繼續往下走了,代碼表現大概以下:
======================================================================
osx的事件響應大致上跟ios同樣的,可是有重要的差異:當事件遍歷到了view B.1,發現point在view B.1裏面,而且view B.1沒有subview,所以它就是咱們要找的hittest view 了。可是hittest並不會中止,會繼續遍歷view A,viewA2也是咱們要找的hitestview。遍歷如圖所示:
mousedown事件響應的順序爲: