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