如果這篇文章幫助到了您,希望您能點擊一下喜歡或者評論,你們的支持是我前進的強大動力.謝謝!
首先看下我們要制作功能的效果如圖所示:
手勢解鎖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
寫的不好的話忘大家指出,一起進步.謝謝!