事件的傳遞與產生

  • 事件是怎么樣產生與傳遞的?
    當發生一個觸摸事件后
    1.系統會將該事件加入到一個由UIApplication管理的事件隊列中.
    2.UIApplication會從事件隊列中取出最前面的事件,交給主窗口.
    3.主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件

  • 觸摸事件的傳遞
    從父控件傳遞到子控件的.
    如果一個父控件不能接收事件,那么它里面的了子控件也不能夠接收事件.

  • 一個控件什么情況下不能夠接收事件?
    1.不接收用戶交互時不能夠處理事件
    userInteractionEnabled = NO
    2.當一個控件隱藏的時候不能夠接收事件
    Hidden = YES的時候
    3.當一個控件為透明度為0的時候也不能夠接收事件,這個控件內部的子控件也不能接受事件
    注意:UIImageView的userInteractionEnabled默認就是NO,
    因此UIImageView以及它的子控件默認是不能接收觸摸事件的

如何尋找最合適的View

  • 判斷步驟
    1.先判斷自己是否能夠接收觸摸事件,如果能再繼續往下判斷,
    2.再判斷觸摸的當前點在不在自己的身上.
    3.如果在自己身上,它會從后往前遍歷子控件,遍歷出每一個子控件后,重復前面的兩個步驟.
    4.如果沒有符合條件的子控件,那么它自己就是最適合的View.
  • 尋找最適合的view,需要實現的方法
    • hitTest方法
      作用:尋找最適合的View
      參數:當前手指所在的點.產生的事件返回值:返回誰, 誰就是最適合的View.
      什么時候用調用:只要一個事件,傳遞給當前控件時, 就會調用這個控件的
      hitTest方法:
      -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
      可以通過在在某一個控件中使用該方法進行判斷,觸摸事件具體是在自己和自己的子控件中哪一個控件中,并獲取這個控件
    • PointInside方法
      作用:判斷point在不在方法調用者上point:必須是方法調用者的坐標系;
      什么時候調用:hitTest方法底層會調用這個方法,判斷點在不在控件上.
      pointInside方法:
      -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
      { return YES;
      }
      注意點:在判斷點是不是在方法調用者上的時候,需要將點的參考坐標系進行轉換,轉換成以方法調用者自身的bounds為參考坐標系
  • hitTest方法底層實現
    作用:當一個事件傳遞給當前View的時候就會調用這個方法.
    當前手指在屏幕上的點
    當前產生的事件
底層實現代碼:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
   // 1.查看自己能不能接收事件
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01)
    {        
          return nil;
    }
  // 2.判斷當前的點在不在自己身上.
    if(![self pointInside:point withEvent:event])
    {        
          return nil;
    }
  // 查看自己是不是最適合的view
  // 從后往前遍歷自己的子控制器.
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >=0; i--) {
       //取出子控件.
        UIView *childView = self.subviews[i];
       //要把當前的點轉換成子控件上的坐標點.
        CGPoint childP =  [self convertPoint:point toView:childView];
        UIView *view = [childView hitTest:childP withEvent:event];
        //如果有值,直接返回,返回的就是最適合的View.
        if (view) {
            return view;     
         }
    }
    //沒有找到比自己更適合的View.自己就是最適合的View 
    return self;
}
  • 練習
    業務邏輯:
    按鈕可以隨著手指拖動而拖動.拖動過程當中,按鈕當中的子控件也跟著拖動.讓超過按鈕
    的子控件也能夠響應事件,一般情況下,當一個控件超過他的父控件的時候,是不能夠接收事件的.
    現在要做的事情就讓超過父控件的按鈕也能夠響應事件.

          實現思路:
          第一步,先辦到讓按鈕能夠跟隨著手指移動而移動.實現按鈕的touchesMoved方法,
                在touchesMoved方法當中,獲得當前手指所在的點.以前上一個點.
                分別計算X軸的偏移量以及Y軸的偏移量.
                然后修改當前按鈕的transform讓按鈕辦到能夠跟隨著手指移動而移動.
          第二步,實現按鈕的hitTest方法.在該方法當中去判斷當前的點在不在按鈕的子控件上.
                如果在按鈕的子控件上.就返回按鈕的子控件如果不在的話, 就保持系統的默認做法.
    

實現代碼:

1.實現點擊添加子控制器
 -(IBAction)chatBtnClick:(id)sender {  
     UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];  
    [btn setImage:[UIImage imageNamed:@"對話框"] forState:UIControlStateNormal];        
    [btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];    
    btn.frame = CGRectMake(self.chatBtn.bounds.size.width * 0.5, -80, 100, 80);
    self.chatBtn.btn = btn;   
    [self.chatBtn addSubview:btn];   
}
2.自定義按鈕,實現拖動按鈕
讓按鈕跟著手指移動而移動.
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    獲取當前手指對象
    UITouch *touch =   [touches anyObject];
    獲取當前手指所在的點
    CGPoint curP =  [touch locationInView:self];
    獲取上一個手指所在的點
    CGPoint preP =  [touch previousLocationInView:self];   
    X軸的偏移量
    CGFloat offsetX = curP.x - preP.x;
    Y軸的偏移量
    CGFloat offsetY = curP.y - preP.y;
    移動
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);   
}
3.攔截hitTest方法
-(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];    }
}

注意:hitTest方法的返回值的問題
hitTest方法中最后返回的self和hitTest練習中最后返回[super hitTest]的原因

    1. hitTest方法返回self
      是通過遞歸調用的方法,
      遍歷每個控件是否是最適合處理觸摸事件的View,當當前控件是最適合的控件時,返回當前控件自己
      如果某個父控件內的子控件不滿足處理觸摸事件的條件,就會返回父控件自己
    1. hitTest練習中返回的[super hitTest]
      如果在hitTest練習中返回的是self,那么當點擊事件觸發后,系統會自動層層遍歷window內部的子控件,
      如果某一子控件重寫了hitTest方法,且返回的是self,那么當點擊這個控件父控件范圍內的點時,
      那么就會返回當前的控件,為了防止此種情況的發生,最后返回的是[super hitTest];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容