您的位置:首页 > 其它

自定义手势解锁锁控件

2016-04-10 16:20 435 查看
一、控件的使用

模仿市面上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、本例用于娱乐、学习、交流
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: