Quartz 2D 屏幕解锁

本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。

上一节中,我讲解了利用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];
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋恨雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值