Quartz 2D 屏幕解锁

转载自:http://blog.csdn.net/sinat_27706697/article/details/46117361



上一节中,我讲解了利用Quartz 2D完成的涂鸦功能,其实主要是利用了贝塞尔曲线来完成的。可以发现,涂鸦效果中,绘制出来的,一般都是曲线效果。这一节,我讲解利用贝塞尔曲线画直线的案例:屏幕解锁。先看看最终效果图。

这个demo就是仿真“支付宝屏幕解锁”的效果。

1. 分析UI, 有三张图片:一张大的背景图片;手指没有滑到区域的按钮,灰白色的圈圈;手指滑到区域的按钮,高亮显示的按钮。注:按钮与按钮之间的连线,是通过代码实现的,因为有各种各样的线,图片实现不现实。

2. 代码实现分析:

1) 应该记录手指滑过的按钮;

2) 应该记录手指当前所在的位置,注意上图中,最后一根线,没有连接到任何按钮,所以手指移动的时候,它是跟着改变的,所以要实时记录当前点的坐标。

3) 当用户手指抬起时,这次的操作就应该算结束了,应该记录滑动信息及完成清屏操作。

代码实现过程:

1. 定义一个View,专门用来实现屏幕解锁功能。

@interface LockView : UIView

@end

2. 定义两个属性变量,用于存储相关信息。

@interface LockView()
/* 存放所有路径经过的button */
@property (nonatomic,strong) NSMutableArray *selectedButtons;
/* 手指所在的当前位置 */
@property (nonatomic,assign) CGPoint currentPoint;
@end

3. 初始化工作及懒加载。 需要说明的是,实现了initWithFrame 和 initWithCoder, 这样的话,不管是通过xib创建的,还是通过纯代码实现的,都可以。

#pragma mark - init
// 代码初始化调用的方法
- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    [self addCircle];
  }
  return self;
}
// StoryBoard或者Xib初始化调用的方法
- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if (self) {
    [self addCircle];
  }
  return self;
}
#pragma mark - lazy load
- (NSMutableArray *)selectedButtons {
  if (_selectedButtons == nil) {
    _selectedButtons = [NSMutableArray array];
  }
  return _selectedButtons;
}

4. 界面上面按钮的创建工作, 需要说明的是 btn.userInteractionEnabled = NO; 这句代码,之所以将按钮的交互效果禁用,是为了让父控件直接响应touches... 那些方法。

#pragma mark - layout subviews
- (void)addCircle {
  for (int i=0;i<9;i++) {
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.userInteractionEnabled = NO;
    [btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal.png"] forState:UIControlStateNormal];
    [btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted.png"] forState:UIControlStateSelected];
    [self addSubview:btn];
  }
}
- (void)layoutSubviews {
  [super layoutSubviews];
  int colBtnCount = 3;
  int btnCount = self.subviews.count;
  CGFloat marginX = (self.frame.size.width - colBtnCount * kImageWidth) / (colBtnCount + 1);
  CGFloat marginY = (self.frame.size.height - colBtnCount * kImageWidth) / (colBtnCount + 1);
  for (int i = 0; i < btnCount; i++) {
    int row = i / colBtnCount;
    int col = i % colBtnCount;
    UIButton *btn = self.subviews[i];
    CGFloat btnX = marginX + col * (kImageWidth + marginX);
    CGFloat btnY = marginY + row * (kImageWidth + marginY);
    CGFloat btnW = kImageWidth;
    CGFloat btnH = kImageWidth;
    btn.tag = i;
    btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
  }
}

5. 触摸事件。 

1) touchesBegan方法中,定义了一个currentBtn变量,用于判断当前手指是否滑动到了界面上的button,如果有的话,就将滑动到的按钮赋值给currentBtn,然后判断,按钮之前有没有被选中(currentBtn.isSelected属性),如果没有,则设置为选中,此时按钮就高亮显示了。

2) touchesMoved方法所作的事情和touchesBegan方法完全一致,所以直接调用touchesBegan方法就可以了。

3)touchesEnded方法,标志此次滑动操作已经结束,所以进行相应的清空操作就可以了。

#pragma mark - touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  UITouch *touch = [touches anyObject];
  CGPoint point =  [touch locationInView:touch.view];
  self.currentPoint = CGPointZero;
  UIButton *currentBtn = nil;
  for (UIButton *btn in self.subviews) {
    if (CGRectContainsPoint(btn.frame, point)) {
      currentBtn = btn;
      break;
    }
  }
  if (currentBtn && !currentBtn.isSelected) {
    currentBtn.selected = YES;
    [self.selectedButtons addObject:currentBtn];
  } else {
    self.currentPoint = point;
  }
  [self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  [self touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  [self.selectedButtons makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)];
  [self.selectedButtons removeAllObjects];
  [self setNeedsDisplay];
}

6. 绘制代码。

1) 在for循环中,遍历所有存放的button,注意,第一个button应该是绘制的起点,所以调用,moveToPoint方法,而其余的应该进行连线工作addLineToPoint方法。

2)连接(为什么永远是直线? 因为path(没有保存手指触摸所经过的点)只连接最后一个按钮的中心到currentPoint这个点。而涂鸦(如果对涂鸦感兴趣的话,可以参照我上一篇博客,请点击这里)是将所有经过的点,保存在path里面,然后画出来)。

3)CGPointEqualToPoint是为了绘制不在按钮范围的线段,比如,上图中的最后一根线段,没有连上任何的按钮,所以手指移动的时候,它跟着移动。

#pragma mark - draw
- (void)drawRect:(CGRect)rect {
  if (self.selectedButtons.count == 0) return;
  UIBezierPath *path = [UIBezierPath bezierPath];
  path.lineWidth = 5;
  path.lineCapStyle = kCGLineCapRound;
  path.lineJoinStyle = kCGLineJoinRound;
  [[UIColor colorWithRed:32/255.0 green:210/255.0 blue:254/255.0 alpha:0.5] set];
  for (int i = 0; i < self.selectedButtons.count; i++) {
    UIButton *btn = self.selectedButtons[i];
    if (i==0) {
      [path moveToPoint:btn.center];
    } else {
      [path addLineToPoint:btn.center];
    }
  }
  if (CGPointEqualToPoint(self.currentPoint, CGPointZero) == NO) {
    [path addLineToPoint:self.currentPoint];
  }
  [path stroke];
}

7.  增加判断密码是否正确功能。

考虑到,这里的屏幕解锁是一个单独的功能,并且有可能多个界面复用,所以我们可以自定义代理,判断密码输入是否正确。

1)改造 LockView。

@class LockView;
@protocol LockViewDelegate<NSObject>

@optional
- (void)lockView:(LockView *)lockView didFinishPath:(NSString *)path;

@end


@interface LockView : UIView

@property (nonatomic,weak) id<LockViewDelegate> delegate;

@end

2) 在touchesEnded方法判断密码正确与否。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if ([self.delegate respondsToSelector:@selector(lockView:didFinishPath:)]) {
    NSMutableString *str = [NSMutableString string];
    for (UIButton *btn in self.selectedButtons) {
      [str appendFormat:@"%ld",(long)btn.tag];
    }
    [self.delegate lockView:self didFinishPath:str];
  }
  [self.selectedButtons makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)];
  [self.selectedButtons removeAllObjects];
  [self setNeedsDisplay];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值