[iOS UI进阶 - 5.0] 手势解锁Demo
A.需求
1.九宫格手势解锁
2.使用了绘图和手势事件
code source: https://github.com/hellovoidworld/GestureUnlockDemo
B.实现
- 使用按钮来处理每个圆点
- 使用代码生成按钮
- 取消按钮点击事件
- 设置普通状态和选中状态的背景图片
- CGRectContainsPoint,移动到按钮范围内改变按钮为选中状态
- 按钮的连接:使用数组存储被选中的所有按钮,画上连线
- 已经连线的按钮不需要再连线
- 触摸结束清空连线和按钮选中状态
- 移动中也要画出线,最后的点用来辅助画移动中的线
- 解决bug:每次触摸开始重置当前画笔位置
- 设置触摸触发选中的按钮内部范围
- 使用tag记录按钮的选中顺序轨迹,触摸结束取得轨迹
- 封装整个手势解锁view成为一个自定义控件
- 封装按钮称为自定类
1.准备基础界面,使用一个UIView作为解锁画面
2.在控制器ViewController设置一下背景图片和状态栏
1 // 2 // ViewController.m 3 // HVWLockView 4 // 5 // Created by hellovoidworld on 15/1/12. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 @interface ViewController () 12 13 @end 14 15 @implementation ViewController 16 17 - (void)viewDidLoad { 18 [super viewDidLoad]; 19 // Do any additional setup after loading the view, typically from a nib. 20 21 // 设置背景 22 self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Home_refresh_bg"]]; 23 } 24 25 /** 设置状态栏样式 */ 26 - (UIStatusBarStyle)preferredStatusBarStyle { 27 return UIStatusBarStyleLightContent; 28 } 29 30 - (void)didReceiveMemoryWarning { 31 [super didReceiveMemoryWarning]; 32 // Dispose of any resources that can be recreated. 33 } 34 35 36 @end
3.自定义解锁画面的类HVWLockView
4.使用代码初始化HVWLockView的子控件—按钮,设置按钮的样式、位置尺寸
1 // 2 // HVWLockView.m 3 // HVWLockView 4 // 5 // Created by hellovoidworld on 15/1/12. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "HVWLockView.h" 10 #import "HVWLockButton.h" 11 12 #define BUTTON_COUNT 9 13 #define BUTTON_COL_COUNT 3 14 15 @implementation HVWLockView 16 17 #pragma mark - 初始化方法 18 /** 使用文件初始化 */ 19 - (id)initWithCoder:(NSCoder *)aDecoder { 20 if (self = [super initWithCoder:aDecoder]) { 21 [self initView]; 22 } 23 return self; 24 } 25 26 /** 使用代码初始化 */ 27 - (instancetype)initWithFrame:(CGRect)frame { 28 if (self = [super initWithFrame:frame]) { 29 [self initView]; 30 } 31 return self; 32 } 33 34 /** 初始化view内的控件(按钮) */ 35 - (void) initView { 36 for (int i=0; i<BUTTON_COUNT; i++) { 37 HVWLockButton *button = [HVWLockButton buttonWithType:UIButtonTypeCustom]; 38 39 // 取消点击时间 40 button.userInteractionEnabled = NO; 41 42 // 设置指标tag,用来记录轨迹 43 button.tag = i; 44 45 // 设置普通状态图片 46 [button setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal]; 47 48 // 设置选中状态图片 49 [button setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected]; 50 51 // 加入按钮到lock view 52 [self addSubview:button]; 53 } 54 } 55 56 /** 设置按钮位置尺寸 */ 57 - (void)layoutSubviews { 58 [super layoutSubviews]; 59 60 // 取出所有按钮 61 for (int i=0; i<self.subviews.count; i++) { 62 HVWLockButton *button = self.subviews[i]; 63 CGFloat buttonWidth = 74; 64 CGFloat buttonHeight = 74; 65 66 // 此按钮所在列号 67 int col = i % BUTTON_COL_COUNT; 68 // 此按钮所在行号 69 int row = i / BUTTON_COL_COUNT; 70 // 等分水平多余空间,计算出间隙 71 CGFloat marginX = (self.frame.size.width - BUTTON_COL_COUNT * buttonWidth) / (BUTTON_COL_COUNT + 1); 72 CGFloat marginY = marginX; 73 74 // x坐标 75 CGFloat buttonX = marginX + col * (buttonWidth + marginX); 76 // y坐标 77 CGFloat buttonY = marginY + row * (buttonHeight + marginY); 78 79 button.frame = CGRectMake(buttonX, buttonY, buttonWidth, buttonHeight); 80 } 81 } 82 83 @end
out:
5.实现触摸事件方法
(1)点击开始,使被点击的按钮改变为选中状态(改变图片)
(2)点击拖曳中,同样使被触碰到的按钮改变为选中状态
(3)点击结束,清空选中状态
(4)小修改:把HVWLockView背景改为透明
HVWLockView:
1 #pragma mark - 触摸事件 2 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 3 UITouch *touch = [touches anyObject]; 4 CGPoint touchLocation = [touch locationInView:touch.view]; 5 6 // 检测哪个按钮被点中了 7 for (HVWLockButton *button in self.subviews) { 8 if (CGRectContainsPoint(button.frame, touchLocation)) { 9 button.selected = YES; 10 } 11 } 12 13 } 14 15 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 16 UITouch *touch = [touches anyObject]; 17 CGPoint touchLocation = [touch locationInView:touch.view]; 18 19 // 检测哪个按钮被点中了 20 for (HVWLockButton *button in self.subviews) { 21 if (CGRectContainsPoint(button.frame, touchLocation)) { 22 button.selected = YES; 23 } 24 } 25 } 26 27 - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 28 // 消除所有按钮选中状态 29 for (HVWLockButton *button in self.subviews) { 30 button.selected = NO; 31 } 32 }
out:
6.画出连接线
(1)优化:将按钮封装为一个类HVWLockButton
1 // 2 // HVWLockButton.m 3 // HVWLockView 4 // 5 // Created by hellovoidworld on 15/1/12. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "HVWLockButton.h" 10 11 @implementation HVWLockButton 12 13 /** 使用文件创建会调用 */ 14 - (id)initWithCoder:(NSCoder *)aDecoder { 15 if (self = [super initWithCoder:aDecoder]) { 16 [self initLockButton]; 17 } 18 return self; 19 } 20 21 /** 使用代码创建会调用 */ 22 - (instancetype)initWithFrame:(CGRect)frame { 23 if (self = [super initWithFrame:frame]) { 24 [self initLockButton]; 25 } 26 return self; 27 } 28 29 /** 初始化 */ 30 - (void) initLockButton { 31 // 取消交互事件(点击) 32 self.userInteractionEnabled = NO; 33 34 // 设置普通状态图片 35 [self setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal]; 36 37 // 设置选中状态图片 38 [self setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected]; 39 } 40 41 @end
(2)使用一个数组来存储已经被选择的按钮
(3)把触碰到的按钮到加入到上述数组中
(4)在绘图方法中把数组内的按钮用线连起来
(5)使用一个成员变量来存储当前触摸位置,画出最后触摸的按钮到现触摸点的线
(6)重复触碰同一个按钮的时候,不用重绘和计算
(7)创建一个代理方法,在触摸结束的时候输出轨迹序列
(8)精简一下代码
1 // 2 // HVWLockView.m 3 // HVWLockView 4 // 5 // Created by hellovoidworld on 15/1/12. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "HVWLockView.h" 10 #import "HVWLockButton.h" 11 12 #define BUTTON_COUNT 9 13 #define BUTTON_COL_COUNT 3 14 15 @interface HVWLockView() 16 17 /** 已选择按钮数组 */ 18 @property(nonatomic, strong) NSMutableArray *selectedButtons; 19 20 /** 触摸位置 */ 21 @property(nonatomic, assign) CGPoint currentTouchLocation; 22 23 @end 24 25 @implementation HVWLockView 26 27 /** 初始化数组 */ 28 - (NSMutableArray *)selectedButtons { 29 if (nil == _selectedButtons) { 30 _selectedButtons = [NSMutableArray array]; 31 } 32 return _selectedButtons; 33 } 34 35 #pragma mark - 初始化方法 36 /** 使用文件初始化 */ 37 - (id)initWithCoder:(NSCoder *)aDecoder { 38 if (self = [super initWithCoder:aDecoder]) { 39 [self initView]; 40 } 41 return self; 42 } 43 44 /** 使用代码初始化 */ 45 - (instancetype)initWithFrame:(CGRect)frame { 46 if (self = [super initWithFrame:frame]) { 47 [self initView]; 48 } 49 return self; 50 } 51 52 /** 初始化view内的控件(按钮) */ 53 - (void) initView { 54 // 设置透明背景 55 self.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0]; 56 57 for (int i=0; i<BUTTON_COUNT; i++) { 58 HVWLockButton *button = [HVWLockButton buttonWithType:UIButtonTypeCustom]; 59 60 // 设置指标tag,用来记录轨迹 61 button.tag = i; 62 63 // 加入按钮到lock view 64 [self addSubview:button]; 65 } 66 } 67 68 /** 设置按钮位置尺寸 */ 69 - (void)layoutSubviews { 70 [super layoutSubviews]; 71 72 // 取出所有按钮 73 for (int i=0; i<self.subviews.count; i++) { 74 HVWLockButton *button = self.subviews[i]; 75 CGFloat buttonWidth = 74; 76 CGFloat buttonHeight = 74; 77 78 // 此按钮所在列号 79 int col = i % BUTTON_COL_COUNT; 80 // 此按钮所在行号 81 int row = i / BUTTON_COL_COUNT; 82 // 等分水平多余空间,计算出间隙 83 CGFloat marginX = (self.frame.size.width - BUTTON_COL_COUNT * buttonWidth) / (BUTTON_COL_COUNT + 1); 84 CGFloat marginY = marginX; 85 86 // x坐标 87 CGFloat buttonX = marginX + col * (buttonWidth + marginX); 88 // y坐标 89 CGFloat buttonY = marginY + row * (buttonHeight + marginY); 90 91 button.frame = CGRectMake(buttonX, buttonY, buttonWidth, buttonHeight); 92 } 93 } 94 95 #pragma mark - 触摸事件 96 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 97 98 [self touchesMoved:touches withEvent:event]; 99 100 } 101 102 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 103 UITouch *touch = [touches anyObject]; 104 CGPoint touchLocation = [touch locationInView:touch.view]; 105 106 // 检测哪个按钮被点中了 107 for (HVWLockButton *button in self.subviews) { 108 109 // 如果触碰到了此按钮 110 if (CGRectContainsPoint(button.touchFrame, touchLocation)) { 111 button.selected = YES; 112 113 // 如果此按钮没有被触碰过才进行处理 114 if (![self.selectedButtons containsObject:button]) { 115 // 加入到数组 116 [self.selectedButtons addObject:button]; 117 } 118 } 119 120 // 当前触摸位置 121 self.currentTouchLocation = touchLocation; 122 } 123 124 // 重绘 125 [self setNeedsDisplay]; 126 } 127 128 - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 129 // 轨迹序列 130 NSMutableString *passPath = [NSMutableString string]; 131 132 // 合成轨迹序列 133 for (HVWLockButton *button in self.selectedButtons) { 134 // 添加到轨迹序列 135 [passPath appendFormat:@"%d", button.tag]; 136 } 137 138 // 调用代理方法 139 if ([self.delegate respondsToSelector:@selector(hvwLockView:didFinishedWithPath:)]) { 140 [self.delegate hvwLockView:self didFinishedWithPath:passPath]; 141 } 142 143 // 清除选中状态 144 [self.selectedButtons makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)]; 145 146 // 清空数组 147 [self.selectedButtons removeAllObjects]; 148 149 // 重绘 150 [self setNeedsDisplay]; 151 } 152 153 154 #pragma mark - 绘图方法 155 - (void)drawRect:(CGRect)rect { 156 UIBezierPath *path = [UIBezierPath bezierPath]; 157 158 // 遍历已选择按钮数组 159 for (int i=0; i<self.selectedButtons.count; i++) { 160 HVWLockButton *button = self.selectedButtons[i]; 161 162 if (0 == i) { 163 [path moveToPoint:button.center]; 164 } else { 165 [path addLineToPoint:button.center]; 166 } 167 } 168 169 if (self.selectedButtons.count) { 170 [path addLineToPoint:self.currentTouchLocation]; 171 } 172 173 // 设置画笔 174 [[UIColor redColor] set]; 175 [path setLineWidth:10]; 176 [path setLineCapStyle:kCGLineCapRound]; 177 [path setLineJoinStyle:kCGLineJoinBevel]; 178 179 [path stroke]; 180 } 181 182 @end
out:
2015-01-12 16:39:23.794 HVWLockView[10274:184387] 手势解锁的输出序列:01246