1. 程式人生 > >UI技術總結--UI事件傳遞&響應

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

返回YES,那麼就會以倒敘的方式遍歷當前檢視的子檢視,遍歷的過程中會呼叫所有自檢視的-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法,如果某個子檢視返回了最終的事件響應檢視的話,就會把對應的檢視作為最終的響應檢視返回給呼叫方,如果返回的是nil的話就會繼續遍歷當前檢視的下一個子檢視.如果全部遍歷完了之後都沒有對應的子檢視去響應事件的話,由於當前點選位置在當前檢視範圍內,就會把當前的檢視作為響應檢視返回給呼叫方.

通過一個例子來更深入的理解一下

在下圖這個正方形按鈕中,只有白色圓型區域可以響應點選事件.

自定義一個button,通過 - (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,如果仍然沒有處理這個事件,那麼最後就會忽略這個事件.

終.