zoukankan      html  css  js  c++  java
  • ios开发事件处理之 四:hittest方法的底层实现与应用

    #import "XMGWindow.h"
    /**
     1:注意点:hitTest方法内部会调用pointInside方法,询问触摸点是否在自己身上,当遍历子控件时,传入的坐标点要进行转化,将父视图上的坐标点转换到所要传递的子视图上的坐标点
     2:hitTest的底层实现:当控件接收到触摸事件的时候,不管能不能处理事件,都会调用hitTest方法,此方法的底层实现是:1:先看自己是否能接受触摸事件  2:再看触摸点是否在自己身上 3:从后往前遍历子控件,拿到子控件后,再次重复1,2步骤,要把父控件上的坐标点转换为子控件坐标系下的点,再次执行hitTest方法。
     
     3:若是最后还没有找到合适的view,那么就return self,自己就是合适的view
     
     
     
     */
    @implementation XMGWindow
    
    
    //作用:去寻找最适合的View
    //什么时候调用:当一个事件传递给当前View,就会调用.
    //返回值:返回的是谁,谁就是最适合的View(就会调用最适合的View的touch方法)
    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
       
        //1.判断自己能否接收事件
        if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
            return nil;
        }
        //2.判断当前点在不在当前View.
        if (![self pointInside:point withEvent:event]) {
            return nil;
        }
        //3.从后往前遍历自己的子控件.让子控件重复前两步操作,(把事件传递给,让子控件调用hitTest)
        int count = (int)self.subviews.count;
        for (int i = count - 1; i >= 0; i--) {
            //取出每一个子控件
            UIView *chileV =  self.subviews[i];
            //把当前的点转换成子控件坐标系上的点.
            CGPoint childP = [self convertPoint:point toView:chileV];
            UIView *fitView = [chileV hitTest:childP withEvent:event];
            //判断有没有找到最适合的View
            if(fitView){
                return fitView;
            }
        }
        
        //4.没有找到比它自己更适合的View.那么它自己就是最适合的View
        return self;
        
    }
    
    
    
    //作用:判断当前点在不在它调用View,(谁调用pointInside,这个View就是谁)
    //什么时候调用:它是在hitTest方法当中调用的.
    //注意:point点必须得要跟它方法调用者在同一个坐标系里面
    -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        NSLog(@"%s",__func__);
        return YES;
    }
    
    
    
    
    
    @end

     2:hitTest 方法的练习1:

    业务逻辑:

    底部一个按钮, 按钮的上面有一个View,遮挡在按钮的上面.

    点击View时, View接收事件,当发现点击的点在按钮的位置时, 让底部的按钮处理事件.

    实现思路:

    实现View的touchBegain方法,先坚听UIView的点击.

    并去实现UIView的HitTest方法, 在hitTest方法当中通过把当前点转换成按钮所在的坐标系

          CGPoint btnP = [self convertPoint:point toView:self.btn];

           转换过后查看当前点在不在按钮上,如果在按钮上,就直接返回按钮.

           如果有在按钮上,保持系统默认做法.

            

        实现代码:

        -(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];

        }

    }

    #import "BlueView.h"
    
    @interface BlueView()
    
    @property(nonatomic, weak) IBOutlet UIButton *btn;
    
    @end
    
    @implementation BlueView
    
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        //NSLog(@"%@",self.btn);
        //return [super hitTest:point withEvent: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];
        }
        
    }
    
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"%s",__func__);
    }
    
    @end

    注意:在storyBoard中定义了view和按钮,view自定义可以拖线到自定义的view中,前提是得进行类的关联,但是button不能拖入,因为button不属于view的子控件,解决:可以在view中属性定义IBOutlet,在拖线到storyBoard中的按钮,这样就在view中拿到了不属于view的button

    2:hitTest练习2

    业务逻辑:
    按钮可以随着⼿手指拖动⽽而拖动.拖动过程当中,按钮当中的⼦子控件也跟着拖动. 让超过按钮的⼦子控件也能够响应事件,⼀一般情况下,当⼀一个控件超过他的⽗父控件的时候,是不能 够接收事件的.
    现在要做的事情就让超过⽗父控件的按钮也能够响应事件.

    #import "ViewController.h"
    #import "chatBtn.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    }
    - (IBAction)btnClick:(chatBtn *)sender {
        
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
        [btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
        
        sender.popBtn = btn;
        
        btn.frame = CGRectMake(100, -80, 100, 80);
        [sender addSubview:btn];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    #import <UIKit/UIKit.h>
    
    @interface chatBtn : UIButton
    
    
    /** 弹出的按钮 */
    @property (nonatomic, weak)UIButton *popBtn;
    
    @end
    #import "chatBtn.h"
    
    @implementation chatBtn
    
    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        if (self.popBtn) {
            //return self.popBtn;
            //判断当前点在不在popBtn身上
            //把当前点转换popBtn身上的点
             CGPoint popBtnP = [self convertPoint:point toView:self.popBtn];
            if ( [self.popBtn pointInside:popBtnP withEvent:event]) {
                return self.popBtn;
            }else{
                return [super hitTest:point withEvent:event];
            }
            
        }else{
          return  [super hitTest:point withEvent:event];
        }
    
    }
    
    
    -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        
        //1.获取UITouch
        UITouch *touch = [touches anyObject];
        //2.获取当前手指的点,上一个手指的点
        CGPoint curP = [touch locationInView:self];
        CGPoint preP = [touch previousLocationInView:self];
        //3.计算偏移量
        CGFloat offsetX = curP.x - preP.x;
        CGFloat offsetY = curP.y - preP.y;
        //4.平移
        self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
        
    }
    
    
    @end

    一般情况下,当⼀一个控件超过他的⽗父控件的时候,是不能 够接收事件的.原因是:1:当产生触摸事件后,系统会将触摸事件发送到由UIApplication管理的事件队列中,UIApplication会将队列中最前端的事件取出来交给keywindow去处理,主窗口keywindow会1:查看自身能不能接受触摸事件 2:触摸点是不是在自身上  3:若是前两个条件都满足则其会遍历自身的子控件,且是从后到前遍历,也就是从子控件数组的最后一个控件开始遍历,在执行前两个步骤,若是不再则遍历下一个子控件,若是一直没找到,则自己就是最合适处理事件的view,若在,则继续重复前两个步骤,直到找到最合适的view。2:当点击按钮的时候window将事件传递到白色view,白色view从后往前遍历子控件,先遍历蓝色的button,在遍历点击对话框找到适合处理事件的view。当子控件超出父控件的范围后,点击子控件时不会响应事件,原因是:当遍历到父控件点击对话框时,触摸点不再其身上,则其就不会响应事件

    实现思路:
    第⼀一步,先办到让按钮能够跟随着⼿手指移动⽽而移动. 实现按钮的touchesMoved⽅方法,在touchesMoved⽅方法当中,获得当前⼿手指所在的点.以前上⼀一 个点.一个手指对应一个UITouch对象,一个手指取出UITouch,anyobject,若是多根手指则会touch.allobjects获取所有的UITouch对象
    分别计算X轴的偏移量以及Y轴的偏移量. 然后修改当前按钮的transform让按钮办到能够跟随着⼿手指移动⽽而移动(累加形变).

    第⼆二步,1: 实现按钮的hitTest⽅方法. 在该⽅方法当中去判断当前的点在不在按钮的⼦子控件上. 如果在按钮的⼦子控件上.就返回按钮的⼦子控件如果不在的话, 就保持系统的默认做法.一般在父类中重写hitTest方法,修改返回最合适的view 2:self.chatBtn.btn定义为弱引用, self.chatBtn.btn = btn赋值指针地址,弱引用指向该对象,[self.chatBtn addSubview:btn];强引用引用着btn使其不被销毁,所以可以用weak 3:事件会由白色view传递到其子控件self.chatBtn上,所以在self.chatBtn的类里重写hitTest方法,修改返回的view

    实现代码: 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];
        }
    

  • 相关阅读:
    Android 一个app启动另一个app
    Android 电池电量进度条,上下滚动图片的进度条(battery)
    Android 返回键双击退出程序
    Failed to load or instantiate
    GNUstep 快捷键编译
    Android 文件夹命名规范 国际化资源
    mac 安装protobuf,并编译为java,c++,python
    Android 贝塞尔曲线 折线图
    android 制作自定义标题栏
    Gson
  • 原文地址:https://www.cnblogs.com/cqb-learner/p/5811462.html
Copyright © 2011-2022 走看看