• 自定义手势解锁锁控件


    一、控件的使用

    模仿市面上app的手势解锁功能,实现的小控件,将控件封装到了一个UIView上

    二、核心原理技术

     1、触摸事件

    (1)UIView的触摸三个触摸响应事件:开始、移动、结束

    (2)CGRectContainsPoint 判断触摸点的位置

    2、Quartz2D绘图

    (1)drawRect 的重绘

    (2)UIBezierPath 贝塞尔曲线

    3、block成功和失败的回调

    三、实现思路

    1、解锁键盘中的9个小图标,会根据验证过程而变化颜色,所以考虑用UIButton实现,因为UIButton可以根据设置不同状态,而获得不同的图片。按钮本身不需要点击事件的实现。

    2、触摸过程中,实现触摸事件的三个方法:

    (1)在开始时判断触摸点是否在某个按钮上,进而改变按钮的状态,从而实现“点亮”。

    (2)建立数组,记录每一个被“点亮”的按钮

    (3)按照按钮的中心点绘制连线

    (4)移动过程中,继续“点亮”按钮并记录

    (5)触摸结束,进行逻辑判断,是否解锁成功,解锁密码用按钮的tag拼接(整数串)。根据成功与否,更改界面上按钮的状态,然后再重绘

    (6)进行成功或失败的回调

    四、源码

    1、.h文件

    @interface ZQGestureUnlockView : UIView
    
    /**
     *  实例化解锁键盘,宽高320*320,9个按钮,背景黑
     *
     *  @param frame    x,y可用
     *  @param password 输入密码由1~9的数组组成的字符串,不可重复
     *  @param success  解锁成功的回调
     *  @param fail     解锁失败的回调
     *
     *  @return 返回手势锁键盘
     */
    +(instancetype)unlockWithFrame:(CGRect)frame Password:(NSString *)password successBlock:(void(^)())success failBlock:(void(^)())fail;
    
    @end

    2、宏定义及私有变量定义

    //按钮显示列数
    #define col 3
    
    //按钮总数
    #define sum 9
    
    //按钮的宽高
    #define iconWH 80
    
    //默认状态下连线的颜色
    #define ZQLineColor [UIColor colorWithRed:0.0 green:170/255.0 blue:255/255.0 alpha:0.5]
    
    @interface ZQGestureUnlockView ()
    
    
    //记录选中的按钮
    @property(nonatomic,strong) NSMutableArray * clickBtnArray;
    
    //记录连线的颜色
    @property(nonatomic,strong) UIColor * lineColor;
    
    //记录时刻的触摸点坐标,时刻是最新的点
    @property(nonatomic,assign) CGPoint currentPoint;
    
    //用户指定的密码
    @property(nonatomic,copy) NSString * password;
    
    //成功回调的block
    @property(nonatomic,copy) void(^success)();
    
    //失败回调 block
    @property(nonatomic,copy) void(^fail)();
    
    
    @end

    3、.m文件中的方法实现

    //初始化方法
    +(instancetype)unlockWithFrame:(CGRect)frame Password:(NSString *)password successBlock:(void(^)())success failBlock:(void(^)())fail{
        ZQGestureUnlockView * mainView = [[self alloc]init]; 
        mainView.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 320);
        mainView.password = password;
        mainView.success = success; 
        mainView.fail = fail;
        
        //生成按钮    
        [mainView setupButtons];
        return mainView;
    }
    
    
    //生成按钮
    -(void)setupButtons
    {
        //生成按钮
        for (int i = 1; i<sum+1; i++) {
            UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];   
            [button setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
            [button setImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateHighlighted];
            [button setImage:[UIImage imageNamed:@"gesture_node_error"] forState:UIControlStateDisabled];
        
            //增加按钮的tag
            button.tag = i ;
            //初始状态,让按钮不可交互,按钮就是显示用的,没有点击事件
            button.userInteractionEnabled=NO;   
            [self addSubview:button];
    }
    //记录一下默认的线的颜色 self.lineColor = ZQLineColor; //设置透明 self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bg"]]; } //布局子控件 -(void)layoutSubviews { [super layoutSubviews]; CGFloat margin = (self.frame.size.width - col*iconWH)/2; [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { CGFloat x= idx%col *(iconWH + margin); CGFloat y= idx/col * (iconWH +margin); obj.frame=CGRectMake(x, y, iconWH, iconWH); }]; } #pragma mark - 触摸开始,记录开始的点 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch * touch = touches.anyObject; CGPoint startPoint = [touch locationInView:self]; //遍历所有按钮,判断触摸的位置是否在button范围内 [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL isContain = CGRectContainsPoint(obj.frame, startPoint); //如果在某个按钮范围内,同时这个按钮没有被点亮 if (isContain && obj.highlighted==NO) { //改变按钮状态为高亮 obj.highlighted = YES; //将这个按钮记录到“选中按钮标记数组” [self.clickBtnArray addObject:obj]; } //如果不在某个按钮内,那么把这个按钮设置为不高亮(不管它是否原来是高亮状态) else { obj.highlighted=NO; } }]; //保存此刻的坐标,用于绘制连线 self.currentPoint = startPoint; } #pragma mark - 触摸移动,同样判断按钮的位置 -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch * touch = touches.anyObject; CGPoint movePoint = [touch locationInView:self]; //判断触摸move的位置是否在button范围内,遍历所有按钮 [self.subviews enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL isContain = CGRectContainsPoint(obj.frame, movePoint); //如果触摸范围在某个按钮上,并且该按钮没有被点亮 if (isContain && obj.highlighted==NO) { obj.highlighted = YES; [self.clickBtnArray addObject:obj]; } }]; //保存此刻的坐标 self.currentPoint = movePoint; //调用view重绘,绘制连线 [self setNeedsDisplay]; } #pragma mark - 触摸结束,进行逻辑判断,重绘连线,显示判断后的连线状态 -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //拼装用户手势结束后的密码 NSMutableString * passWordString =[NSMutableString string]; [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //合成密码 [passWordString appendFormat:@"%ld",obj.tag]; }]; //证明密码正确 if ([passWordString isEqualToString:self.password]) { //加延迟,是为了密码正确后给一个短暂的停顿 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //先把按钮取消高亮 [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.highlighted=NO; }]; //再把数组清空 [self.clickBtnArray removeAllObjects]; //最后重绘 [self setNeedsDisplay]; }); //执行验证成功的回调 self.success(); } //密码错误 else { //首先关闭view交互,避免错误操作 self.userInteractionEnabled=NO; //先把按钮高亮去掉,设置成enabled状态,然后把连线变红 [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.enabled=NO; obj.highlighted=NO; }]; self.lineColor = [UIColor redColor]; [self setNeedsDisplay]; //延迟一下再清除 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //先把按钮enable [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.enabled=YES; }]; //再把高亮按钮数组清空 [self.clickBtnArray removeAllObjects]; //重绘 [self setNeedsDisplay]; //最后在开启交互 self.userInteractionEnabled=YES; //还原默认连线颜色 self.lineColor=ZQLineColor; }); } //将currentPoint 设置成和最后的高亮按钮中心一样,这样可以在释放触摸后,连线不显示多余的“尾巴” UIButton * btn = self.clickBtnArray.lastObject; self.currentPoint= btn.center; } //view的重绘 -(void)drawRect:(CGRect)rect { UIBezierPath * path = [UIBezierPath bezierPath]; [self.clickBtnArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //设置起始点 if (idx==0) { [path moveToPoint:obj.center]; } //设置增加点 else { [path addLineToPoint:obj.center]; } }]; //线条颜色、宽 [path setLineWidth:10]; UIColor * color = self.lineColor; [color setStroke]; //设置连接 [path setLineJoinStyle:kCGLineJoinRound]; [path setLineCapStyle:kCGLineCapRound]; //再添加实时的“线头” if (self.clickBtnArray!=nil) { [path addLineToPoint:self.currentPoint]; } //绘图 [path stroke]; } //懒加载 -(NSMutableArray *)clickBtnArray { if (!_clickBtnArray) { _clickBtnArray=[NSMutableArray array]; } return _clickBtnArray; }

    五、扩展

    1、解锁键盘的大小可以根据实际情况定义

    2、触摸手势的绘制采用的是不可重复的点,这个可以改进

    3、不限验证次数 

    4、本例用于娱乐、学习、交流

  • 相关阅读:
    UIrecorder脚本录制与回放
    通过jmeter读取csv的case来实现接口自动化
    Jmeter操作MySQL数据库详解
    使用docker安装Jenkins
    CMMS / EAM与工厂其它业务流程的交集,融合趋势有哪些
    如何让CMMS系统适应您的企业
    CMMS系统中的物联监测
    CMMS系统中工单派案&调度
    移动化的设备维护管理系统--移动端界面
    物流行业设备维护管理系统
  • 原文地址:https://www.cnblogs.com/cleven/p/5374429.html
Copyright © 2020-2023  润新知