自定义手势解锁锁控件

一、控件的使用

模仿市面上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、本例用于娱乐、学习、交流

时间: 2024-10-06 19:29:45

自定义手势解锁锁控件的相关文章

android自定义手势解锁View

有时候为了程序的安全性,我们经常要采取一些安全措施,就像我们常用的支付宝那样,隔一定的时间再回到应用程序时会让用户利用手势去解锁应用程序,最近由于项目需求,也要求做这样一个功能,当用户切出本应用程序15分钟后回来,让用户手势解锁,整个需求的难点就在如何实现这个手势锁,开始一点头绪也没有,没有一点思路去实现这个手势解锁功能,在google了一番后看了一篇非常好的博客后,按照博主的思路的确是可以实现一个十分不错的手势锁View,也参考了下那位大神的代码,下面是我根据他的思路和代码片段实现的一个自定义

Android手势解锁

给大家介绍一个很好用的第三方手势解锁控件ShapleLocker, 废话不多先上效果图: 可自己根据UI需求替换图标: 圆圈, 小箭头等等.. github地址: http://panespanes.github.io/ShapeLocker/ 用法很简单, 首先在gradle中添加依赖: 在项目(project, 不是module)的build.gradle中, respositories的最后加上一行引用jitpack的maven仓库地址 allprojects { repositories

Android进阶之自定义View实战(二)九宫格手势解锁实现

一.引言 在上篇博客Android进阶之自定义View实战(一)仿iOS UISwitch控件实现中我们主要介绍了自定义View的最基本的实现方法.作为自定义View的入门篇,仅仅介绍了Canvas的基本使用方法,而对用户交互层面仅仅处理了单击事件接口,在实际的业务中,常常涉及到手势操作,本篇博客以九宫格手势解锁View为例,来说明自定义View如何根据需求处理用户的手势操作.虽然九宫格手势解锁自定义View网上资料有很多,实现原理大同小异,但这里我只是根据自己觉得最优的思路来实现它,目的是让更

九点(九宫格)式手势解锁自定义view

周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事.把源码和资源贴出来,给大家分享,希望对大家有用. 效果,就跟手机上的九点手势解锁一样,上个图吧: 过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了.都在代码里,所以这里就主要是贴资源吧. 这个自定义view就一个类,源码如下: package com.cc.library.view; import android.

iOS开发之手势解锁

本文主要介绍通过手势识别实现手势解锁功能,这个方法被广泛用于手机解锁,密码验证,快捷支付等功能实现.事例效果如下所示. 首先,我们先分析功能的实现过程,首先我们需要先看大致的实现过程: 1.加载九宫格页面 2.实现按钮被点击及滑动过程中按钮状态的改变 3.实现滑动过程中的连线 4.绘制完毕后判定密码是否正确, 5.密码判定后实现跳转. 下面我们就来用代码实现上述五个过程. 1.加载九宫格界面 1.1九宫格内控件的分布 3*3 ,我们可以自定义view(包含3*3个按钮),添加到viewContr

手势解锁步骤的基本思路

------------- 基本思路 -------------- - 1. 搭建界面,九宫格算法 - 2. 处理按钮选中状态 - 3. 按钮之间画线 - 4. 手指和按钮之间画线 - 5. 判断解锁密码是否正确 ------------- 基本思路 -------------- 1. 拖拽图片素材 2. 设置控制器 view 的背景色为"HomeButtomBG"图片平铺后的效果 self.view.backgroundColor = [UIColor colorWithPatter

手势解锁的实现思路

------------- 基本思路 -------------- - 1. 搭建界面,九宫格算法 - 2. 处理按钮选中状态 - 3. 按钮之间画线 - 4. 手指和按钮之间画线 - 5. 判断解锁密码是否正确 ------------- 基本思路 -------------- 1. 拖拽图片素材 2. 设置控制器 view 的背景色为"HomeButtomBG"图片平铺后的效果 self.view.backgroundColor = [UIColor colorWithPatter

iOS开发UI篇—实现一个简单的手势解锁应用(基本)

iOS开发UI篇—实现一个简单的手势解锁应用(基本) 一.实现效果 实现效果图: 二.手势解锁应用分析 1.监听手指在view上的移动,首先肯定需要自定义一个view,重写touch began,touch move等方法,当手指移动到圈上时,让其变亮.可以通过button按钮来实现. 2.界面搭建 背景图片(给控制器的view添加一个imageview,设置属性背景图片) 九个按钮(把九个按钮作为一个整体,使用一个大的view来管理这些小的view,这些小的view就是9个button.如果使

[iOS UI进阶 - 5.0] 手势解锁Demo

A.需求 1.九宫格手势解锁 2.使用了绘图和手势事件 code source: https://github.com/hellovoidworld/GestureUnlockDemo B.实现 使用按钮来处理每个圆点 使用代码生成按钮 取消按钮点击事件 设置普通状态和选中状态的背景图片 CGRectContainsPoint,移动到按钮范围内改变按钮为选中状态 按钮的连接:使用数组存储被选中的所有按钮,画上连线 已经连线的按钮不需要再连线 触摸结束清空连线和按钮选中状态 移动中也要画出线,最后