UI技術總結--UI事件傳遞&響應
在講述UI事件傳遞之前,先要知道UIView 和 UILayer的區別是什麼.簡而言之
- UIView為其提供內容,以及負責處理觸控等事件,參與響應鏈
- CALayer負責顯示內容contents UIView只負責事件傳遞和檢視響應鏈,而顯示部分的內容都是由CALayer來負責, 這提現了系統設計UIView和CALayer中所運用的一個設計原則,就是單一職責原則.
接下來看看這幅圖
- 思考一下,當點選圖中白圈那個位置的時候,事件是如何進行傳遞的呢.
事件的傳遞主要和兩個方法有關
// 返回哪個檢視響應這個事件,返回nil表示不處理該事件
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// 判斷某一個點選的位置是否在檢視範圍內
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
複製程式碼
接下來來看看這個流程圖
首先,先判斷檢視的使用者互動是否開啟、是否隱藏、透明度是否大於0.01,如果有一個不符合,那麼該檢視將不處理任何事件響應,也不會進行後續的操作了.如果這三個條件都滿足了,就會呼叫當前檢視的-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
方法來判斷點選的點是否在當前檢視範圍內,如果不在的話也會返回nil再由它當前檢視的父檢視去遍歷它的同級兄弟檢視,呼叫對應的-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法.如果當前檢視的-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法,如果某個子檢視返回了最終的事件響應檢視的話,就會把對應的檢視作為最終的響應檢視返回給呼叫方,如果返回的是nil的話就會繼續遍歷當前檢視的下一個子檢視.如果全部遍歷完了之後都沒有對應的子檢視去響應事件的話,由於當前點選位置在當前檢視範圍內,就會把當前的檢視作為響應檢視返回給呼叫方.
通過一個例子來更深入的理解一下
在下圖這個正方形按鈕中,只有白色圓型區域可以響應點選事件.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
和
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
方法來實現上訴功能.
#import "CustomButton.h"
@implementation CustomButton
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.userInteractionEnabled ||
[self isHidden] ||
self.alpha <= 0.01) {
return nil;
}
// 如果點選區域在白色圓形區域內,則返回self,否則不響應事件
if ([self pointInside:point withEvent:event]) {
return self;
}
else{
return nil;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat x1 = point.x;
CGFloat y1 = point.y;
CGFloat x2 = self.frame.size.width / 2;
CGFloat y2 = self.frame.size.height / 2;
// 這就是傳說中的勾股定理
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (dis <= self.frame.size.width / 2) {
return YES;
}
else{
return NO;
}
}
@end
複製程式碼
好了,現在這個自定義的button就實現了上述的功能了. 回到最開始的問題,點選那個白色位置,事件是如何進行傳遞的呢.如下圖:
首先點選位置在C2、B2、和A區域內,那麼這3個檢視都是有可能響應事件的.如果C2是B2的子檢視,B2是A的子檢視的話,那麼按照順序應該先呼叫子檢視的-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法,而如果他們三個檢視是平級的話,按照同級檢視倒敘遍歷的方式,也是C2先響應(因為C2覆蓋在B2和A上).所以順序就是C2先執行-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法如果返回nil,B2再執行,最後才是A.如果都沒有處理這個事件,那麼最後會傳到UIApplication,如果仍然沒有處理這個事件,那麼最後就會忽略這個事件.