項目源碼下載
事件的產生與傳遞
-
事件是如何產生與傳遞的?
- 當發(fā)生觸摸事件后,系統會將該事件加入到一個由UIApplication管理的事件隊列中. UIApplication會從時間隊列中取出最前面的時間,并將事件分發(fā)下去以便處理.主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸時間.
- 觸摸時間的傳遞是從父控件傳遞到子控件的,如果一個父控件不能接收事件,那么他里面的子控件也不能接收.
-
當一個控件不能接收時間時一般有以下幾種情況
1.不接收用戶交互userInteractionEnabled = NO
2.當一個控件隱藏時Hidden = YES
3.當一個控件為透明白時注意:
UIImageView
以及它的子控件默認是不能接收觸摸事件的
事件的響應
用戶點擊屏幕產生的一個觸摸事件,經過一系列的傳遞過程后,會找到一個最適合的視圖來處理事件.找到最合適的視圖控件后,就會調用控件的touches
方法來作具體的時間處理.touches
的默認做法是將事件順著響應者鏈條向上傳遞,將事件交給上一個響應者處理
- 什么是響應者鏈條?
- 由多個響應者對象連接起來的鏈條
- 什么是響應者對象?
- 繼承了
UIResponder
的對象
- 繼承了
-
如何去尋找上一個響應者
1.如果當前的View是控制器的View,那么控制器就是上一個響應者
2.如果當前的View不是控制器的View,那么他的父控件就是上一個響應者
3.在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
4.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
5.如果UIApplication也不能處理該事件或消息,則將其丟棄
系統是如何尋找最合適的View
1.先判斷自己是否能接收觸摸事件
2.再判斷觸摸的當前點在不在自己身上
3.如果在自己身上,它會從后往前遍歷子控件,遍歷出每一個控件后,重啟前兩步
4.如果沒有符合條件的子控件,那么自身就是最合適的View
在尋找最合適View的過程中,系統會調用2個方法
//作用:尋找最適合的View
//什么時候調用:當事件傳遞給當前View時就會調用這個方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *fitView = [super hitTest:point withEvent:event];
NSLog(@"%@",fitView);
return fitView;
}
//作用:判斷觸摸點在不在當前的View上.
//什么時候調用:在hitTest方法當中會自動調用這個方法.
//注意:point必須得要跟當前View同一個坐標系.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return YES;
}
那么hitTest: withEvent:
方法底層是如何實現的呢?
// 判斷自己能否接收事件
if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01){
return nil;
}
// 觸摸點在不在自己身上
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
// 從后往前遍歷自己的子控件(重復前面的兩個步驟)
int count = (int)self.subviews.count;
for (int i = count -1; i >= 0; i--) {
UIView *childV = self.subviews[i];
// point必須得要跟childV相同的坐標系.
// 把point轉換childV坐標系上面的點
CGPoint childP = [self convertPoint:point toView:childV];
UIView *fitView = [childV hitTest:childP withEvent:event];
if (fitView) {
return fitView;
}
}
// 如果沒有符合條件的子控件,那么就自己最適合處理
return self;
在開發(fā)中或多或少會需要一些特殊的點擊,這里有2個小例子供大家參考
- 一個按鈕被一個半透明的View部分遮擋,需要點擊到按鈕的時候,按鈕始終響應
- 一個View超出了父視圖的范圍,需要點擊超出范圍的View也有響應
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//當觸摸點在按鈕上的時候,才讓按鈕去響應事件.
//把當前點轉換成按鈕坐標系上的點.
CGPoint btnP = [self convertPoint:point toView:self.btn];
if ( [self.btn pointInside:btnP withEvent:event]) {
return self.btn;
}else{
return [super hitTest:point withEvent:event];
}
}
總結:核心代碼思路都是相同的,將當前點轉換為坐標系上的點,具體代碼可以文章開頭的項目源碼