事件是怎么樣產生與傳遞的?
當發生一個觸摸事件后
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方法
- 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]的原因
- hitTest方法返回self
是通過遞歸調用的方法,
遍歷每個控件是否是最適合處理觸摸事件的View,當當前控件是最適合的控件時,返回當前控件自己
如果某個父控件內的子控件不滿足處理觸摸事件的條件,就會返回父控件自己
- hitTest方法返回self
- hitTest練習中返回的[super hitTest]
如果在hitTest練習中返回的是self,那么當點擊事件觸發后,系統會自動層層遍歷window內部的子控件,
如果某一子控件重寫了hitTest方法,且返回的是self,那么當點擊這個控件父控件范圍內的點時,
那么就會返回當前的控件,為了防止此種情況的發生,最后返回的是[super hitTest];
- hitTest練習中返回的[super hitTest]