如何实现一个圆形进度条按钮

2018-07-10  本文已影响131人  沙琪玛dd
效果展示.gif

预备知识

实现分析

  1. 圆环放大的过程,如何保证圆环放大的时候宽度不变
  2. 进度条绘制的过程

具体实现

//最大录制时间
static float const maxRecordTime = 10.f;
//初始半径
static float const startRadius = 35.f;
//最大半径
static float const endRadius = 50.f;
//圆圈宽度
static float const lineWidth = 6.f;

@interface KiraCircleButton ()

@property (nonatomic, strong) CAShapeLayer *circleLayer;//白色圆圈图层
@property (nonatomic, strong) CAShapeLayer *maskLayer;//遮罩图层
@property (nonatomic, strong) CAShapeLayer *drawLayer;//进度条绘制图层

@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) CGFloat currentRadius; //当前半径
@property (nonatomic, strong) UIVisualEffectView* effectView;
@property (nonatomic, assign) CGPoint centerPoint;
@property (nonatomic, assign) float currentRecordTime;

@end

- (void)initUI {
    self.centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
    [self setUserInteractionEnabled:YES];
//添加手势
    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doSomeThingWhenTap)];
    [self addGestureRecognizer:tap];
    
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(doSomeThingWhenLongTap:)];
    longPress.minimumPressDuration = 0.2;
    longPress.delegate = self;
    [self addGestureRecognizer:longPress];
    
    //给按钮增加毛玻璃效果
    UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular];
    self.effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
    self.effectView.userInteractionEnabled = NO;
    [self addSubview:self.effectView];
    [self.effectView setFrame:CGRectMake(startRadius - endRadius, startRadius - endRadius, 2 * endRadius, 2 * endRadius)];
    
    UIBezierPath *backPath = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius:endRadius - lineWidth/2 startAngle:- M_PI_2 endAngle:3 * M_PI_2 clockwise:YES];
    
    CAShapeLayer *secondLayer = [CAShapeLayer layer];
    secondLayer.strokeColor = [UIColor colorWithRed:1 green:64.f/255.f blue:64.f/255.f alpha:1].CGColor;
    secondLayer.lineWidth = lineWidth;
    secondLayer.fillColor = [UIColor clearColor].CGColor;
    secondLayer.path = backPath.CGPath;
    secondLayer .strokeStart = 0;
    secondLayer.strokeEnd = 0;

    _drawLayer = secondLayer;
    _circleLayer = [CAShapeLayer layer];
    _maskLayer = [CAShapeLayer layer];
    [self resetCaptureButton];
    [self.layer addSublayer:_circleLayer];
    [self.layer setMask:_maskLayer];
    [self.layer addSublayer:secondLayer];
}
- (void)doSomeThingWhenLongTap:(UILongPressGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateBegan) {
        NSLog(@"Im long tapped start");
        self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                       selector:@selector(changeRadius)];
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        self.displayLink.paused = NO;
        if (self.delegate && [self.delegate respondsToSelector:@selector(startProgress)]) {
            [self.delegate startProgress];
        }
    } else if (gesture.state == UIGestureRecognizerStateEnded) {
        NSLog(@"Im long tapped end");
        //end
        self.displayLink.paused = YES;
        if (self.delegate && [self.delegate respondsToSelector:@selector(endProgress)]) {
            [self.delegate endProgress];
        }
        [self resetCaptureButton];
    }
}
- (void)changeRadius {
    CGFloat toValue = endRadius - lineWidth/2;
    CGFloat fromValue = startRadius - lineWidth/2;
    CGFloat duration = 0.2;
    CGFloat times = 60 * duration;
    CGFloat delta = (toValue - fromValue) / times;
    _currentRecordTime += 1.f/60;
    _currentRadius += delta;
    if (_currentRadius <= toValue) {
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius: _currentRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        _circleLayer.path = path.CGPath;
        _circleLayer.lineWidth = lineWidth;

        UIBezierPath *maskPath = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius:_currentRadius + 2 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        _maskLayer = [CAShapeLayer layer];
        _maskLayer.path = maskPath.CGPath;
        [self.layer setMask:_maskLayer];
    } else {
        CGFloat delta = 1 / (maxRecordTime * 60);
        self.drawLayer.strokeEnd += delta;
        if (self.drawLayer.strokeEnd >= 1) {
            self.displayLink.paused = YES;
            if (self.delegate && [self.delegate respondsToSelector:@selector(endProgress)]) {
                [self.delegate endProgress];
            }
        }
    }
    
}

最后

附上 demo链接 ,demo实现了上述的效果,但是并没有封装成一个复用性比较高的控件,主要以提供实现思路为主。有任何疑问欢迎指出😄

补充更新: 2019/02/17
  1. 勘误:CADisplayLink 的刷新频率确实是60次/秒左右,但是并不固定,由于每次调用 CADisplayLink 的时间间隔都不是平均的,所以我们不能根据调用次数乘以1/60的时间间隔来得到当前经历的时间。正确计算当前经历时间的方法是通过获取当前时间再减去起始时间来得到。
  2. 扩展:出于 KiraCircleButton Demo的易用性、可维护性、扩展性考虑,我对代码进行了更新,动画支持配置不同缓动函数,Demo地址不变,如果需要看第一版的代码的话可以切到最初的提交阅读。有关新的 KiraCircleButton 的设计实现介绍请点击CADisplayLink 动画进阶
上一篇 下一篇

猜你喜欢

热点阅读