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,如果仍然沒有處理這個事件,那麼最後就會忽略這個事件.

終.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章