如果这篇文章帮助到了您,希望您能点击一下喜欢或者评论,你们的支持是我前进的强大动力.谢谢!
首先看下我们要制作功能的效果如图所示:
手势解锁4.gif
第一步:界面搭建
- 在storyboard中的控制器的view中放一张与view相中大小的UIImageView并设置图片如效果图所示,然后再控制器的view中再添加一个大小合适UIView来存放9个按钮子控件.
-
代码实现添加按钮:创建一个类继承自UIView,并将这个类和上面storyboard中添加的UIView向关联.
Snip20160302_2.png - 界面是一个九宫格的布局.九宫格实现思路.(需要一点数学思想哈,看的有点模糊的最好画图)
- 先确定有多少列 cloum = 3;
- 计算出每列之间的距离
- 计算为: CGFloat margin = (当前View的宽度 - 列数 * 按钮的宽度) / (总列数 + 1)
- 每一列的X的值与它当前所在的行有关
- 当前所在的列为:curColum = i % cloum
- 每一行的Y的值与它当前所在的行有关.
- 当前所在的行为:curRow = i / cloum
- 每一个按钮的X值为, margin + 当前所在的列 * (按钮的宽度+ 每个按钮之间的间距)
- 每一个按钮的Y值为 当前所在的行 * (按钮的宽度 + 每个按钮之间的距离)
在创建的UIView类中实现以下代码:
由于UIView是从storyboard中加载的,所以初始化使会调用这个方法
-(void)awakeFromNib{
初始化
[self setUP];
}
初始化
- (void)setUP{
for (int i = 0; i < 9; i++) {
添加按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
设置图片
[btn setImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
设置选中状态的下图片
[btn setImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected];
添加按钮
[self addSubview:btn];
}
}
布局子控件
- (void)layoutSubviews{
[super layoutSubviews];
总列数
int cloumn = 3;
按钮高宽
CGFloat btnWH = 74;
每列之间的间距
CGFloat margin = (self.bounds.size.width - cloumn * btnWH) / (cloumn + 1);
当前所在的列
int curClounm = 0;
当前所在的行
int curRow = 0;
CGFloat x = 0;
CGFloat y = 0;
取出所有的控件
for (int i = 0; i < self.subviews.count; i++) {
计算当前所在的列
curClounm = i % cloumn;
计算当前所在的行.
curRow = i / cloumn;
计算Y
x = margin + (margin + btnWH) * curClounm;
计算Y.
y = (margin +btnWH) * curRow;
UIButton *btn = self.subviews[i];
btn.frame = CGRectMake(x, y, btnWH, btnWH);
}
}
第二步:设置按钮选中的状态
Snip20160302_7.png/**
* 获取当前手指所在的点
*
* @param touches touches集合
*
* @return 当前手指所在的点.
*/
- (CGPoint)getCurrentPoint:(NSSet *)touches{
UITouch *touch = [touches anyObject];
return [touch locationInView:self];
}
/**
* 判断一个点在不在按钮上.
*
* @param point 当前点
*
* @return 如果在按钮上, 返回当前按钮, 如果不在返回nil.
*/
- (UIButton *)btnRectContainsPoint:(CGPoint)point{
遍历所有的子控件
for (UIButton *btn in self.subviews) {
判断手指当前点在不在按钮上.
if (CGRectContainsPoint(btn.frame, point)) {
在按钮上.返回当前按钮
return btn;
}
}
return nil;
}
手指点击时让按钮成选中状态
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态.
(我将下面1,2两个方法按功能模块单独抽取出来.)
1.获取当前手指所在的点
CGPoint curP = [self getCurrentPoint:touches];
2.判断当前手指所在的点在不在按钮上.
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn) {
btn.selected = YES;
}
}
手指移动时,按钮选中,连线到当前选中的按钮
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态.
1.获取当前手指所在的点
CGPoint curP = [self getCurrentPoint:touches];
2.判断当前手指所在的点在不在按钮上.
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn) {
btn.selected = YES;
}
}
第三步:连线
@interface ClockView()
/**
* 选中的按钮数组.
* 每次选中一个按钮时,都把按钮添加到数组当中.移动添加到按钮当中时做一次重绘.
* 重绘过程中取出所有保存的按钮, 判断是不是第一个按钮, 如果是第一个按钮,那就让它成为路径的起点.
* 如果不是第一个按钮,那就添加一根线到按钮的中心点.
*/
@property(nonatomic,strong)NSMutableArray *selectBtn;
@end
懒加载数组.
-(NSMutableArray *)selectBtn{
if (_selectBtn == nil) {
_selectBtn = [NSMutableArray array];
}
return _selectBtn;
}
手指点击时让按钮成选中状态
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态.
1.获取当前手指所在的点
CGPoint curP = [self getCurrentPoint:touches];
2.判断当前手指所在的点在不在按钮上.
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn && btn.selected == NO) {如果按钮已经是选中状态,就不让它再添加到数组当中
让按钮成为选中状态
btn.selected = YES;
把选中按钮添加到数组当中
[self.selectBtn addObject:btn];
}
}
手指移动时,按钮选中,连线到当前选中的按钮
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态.
1.获取当前手指所在的点
CGPoint curP = [self getCurrentPoint:touches];
2.判断当前手指所在的点在不在按钮上.
UIButton *btn = [self btnRectContainsPoint:curP];
if (btn && btn.selected == NO) {//如果按钮已经是选中状态,就不让它再添加到数组当中
让按钮成为选中状态
btn.selected = YES;
把选中按钮添加到数组当中
[self.selectBtn addObject:btn];
}
每次手指移动时做一次重绘.
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
创建路径.
UIBezierPath *path = [UIBezierPath bezierPath];
取出所有保存的选中按钮连线.
for(int i = 0; i < self.selectBtn.count;i++){
UIButton *btn = self.selectBtn[i];
判断当前按钮是不是第一个,如果是第一个,把它的中心设置为路径的起点.
if(i == 0){
设置起点.
[path moveToPoint:btn.center];
}else{
添加一根线到当前按钮的圆心.
[path addLineToPoint:btn.center];
}
}
设置颜色
[[UIColor redColor] set];
设置线宽
[path setLineWidth:10];
设置线的连接样式
[path setLineJoinStyle:kCGLineJoinRound];
绘制路径.
[path stroke];
}
第四步:最后的业务逻辑
- 实现以上功能后虽然能实现连线的功能,但是连线只能在按钮之间,按钮与手指之间并不能实现连线.下面就来处理这个问题并实现一些收尾的工作
@interface ClockView()
/**
* 选中的按钮数组.
*/
@property(nonatomic,strong)NSMutableArray *selectBtn;
/**
* 当前手指移动的点
* 记录当前手指的点,数组当中所有的点都绘制完毕后, 再添加一根线到当前手指所在的点.
*/
@property(nonatomic,assign)CGPoint curP;
@end
手指松开时,按钮取消选中状态,清空所有的连线.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
1.取消所有选中的按钮,查看选中按钮的顺序(根据创建按钮时绑定的tag值)
NSMutableString *str = [NSMutableString string];
for (UIButton *btn in self.selectBtn) {
[str appendFormat:@"%ld",btn.tag];
btn.selected = NO;
}
2.清空所有的连线.
[self.selectBtn removeAllObjects];
3.重绘
[self setNeedsDisplay];
NSLog(@"选中按钮顺序为:%@",str);
}
- (void)drawRect:(CGRect)rect {
如果数组当中没有元素,就不让它进行绘图.直接返回.
if(self.selectBtn.count <= 0) return;
创建路径.
UIBezierPath *path = [UIBezierPath bezierPath];
取出所有保存的选中按钮连线.
for(int i = 0; i < self.selectBtn.count;i++){
UIButton *btn = self.selectBtn[i];
判断当前按钮是不是第一个,如果是第一个,把它的中心设置为路径的起点.
if(i == 0){
设置起点.
[path moveToPoint:btn.center];
}else{
添加一根线到当前按钮的圆心.
[path addLineToPoint:btn.center];
}
}
连完先中的按钮后, 在选中按钮之后,添加一根线到当前手指所在的点.
[path addLineToPoint:self.curP];
设置颜色
[[UIColor redColor] set];
设置线宽
[path setLineWidth:10];
设置线的连接样式
[path setLineJoinStyle:kCGLineJoinRound];
绘制路径.
[path stroke];
}
Demo已上传
地址:http://git.oschina.net/li_xiao_nan/overhand
写的不好的话忘大家指出,一起进步.谢谢!
网友评论
不知道为啥,我写的这个有时灵,有时不灵。
计算Y
x = margin + (margin + btnWH) * curClounm;
计算Y.
y = (margin +btnWH) * curRow;