播放环形进度条

155 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

  • 播放按钮,需要根据播放的进度,在按钮上显示事实的进度

思考

  • 进度条用贝塞尔曲线画一个圆,从0到2π 根据进度换算成对应的角度,然后layout button达到事实更新的效果
  • 注意📢: 圆形的半径 要减去border的宽度,要不然圆环到按钮外面去了, layer.masktobound = no的时候完全看不见。上代码
#define RGBA(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)]
#define   DEGREES_TO_RADIANS(degrees)  ((M_PI * (degrees))/ 180)

- (**void**)drawRect:(CGRect)rect{

    

    CGSize size = **self**.bounds.size; // 视图的size

    CGPoint arcCenter = CGPointMake(size.width/2, size.height/2);   // 圆心坐标

    CGFloat circleLineWidth = 4;    // 圆线宽

    CGFloat radius = size.width/2 - circleLineWidth/2// 圆半径

    

    CGFloat startRadian = 0;         // 开始弧度

    CGFloat progressDegree = 0;      // 完成比的角度

    CGFloat endRadian = 0;           // 结束弧度

    

    // 画大圆

    startRadian = DEGREES_TO_RADIANS(-90);

    progressDegree = 1 * 360;

    endRadian = DEGREES_TO_RADIANS(progressDegree-90);

    

    UIBezierPath *whileCircle = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:radius startAngle:startRadian endAngle:endRadian clockwise:**YES**];

    [[UIColor clearColor] setStroke];

    whileCircle.lineCapStyle = kCGLineCapRound;

    whileCircle.lineWidth = circleLineWidth;

    [whileCircle stroke];

    

    // 画圆弧

    startRadian = DEGREES_TO_RADIANS(-90);

    progressDegree = **self**.progress * 360;

    endRadian = DEGREES_TO_RADIANS(progressDegree-90);

    

    UIBezierPath *arcCircle = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:radius startAngle:startRadian endAngle:endRadian clockwise:**YES**];

    [RGBA(255, 59, 48, 1) setStroke];

    arcCircle.lineCapStyle = kCGLineCapRound;

    arcCircle.lineWidth = circleLineWidth;

    [arcCircle stroke];

}

轻松搞定,然而,人算不如天算。圆环是画在按钮上的。button设置图片的时候,图片完全把圆环给遮住了

解决方案

  • 没办法,那就把圆环画在imageView上,然后把imageview添加到button上。多了一层视图。但是能更简单的解决问题,还能防止后面产品更改其他的需求
-(**void**)layoutSubviews {

    [**super** layoutSubviews];

    CGFloat startRadian = DEGREES_TO_RADIANS(-90);

//    progressDegree = self.progress * 360;

    CGFloat endRadian = DEGREES_TO_RADIANS(0-90);

    CGFloat width = **self**.width;

    UIBezierPath *backgroundBezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(width/2.0f, width/2.0f) radius:(CGRectGetWidth(**self**.bounds)- **self**.trackWidth)/2.f startAngle:startRadian endAngle:endRadian

                                                       clockwise:**YES**];

    _backgroundLayer.path = backgroundBezierPath.CGPath;

    

    //设置线宽

    _frontFillLayer.lineWidth = **self**.trackWidth;

    _backgroundLayer.lineWidth = **self**.trackWidth;

}

- (**void**)setProgress:(CGFloat)progress startAngle:(CGFloat )startAngle{
CGFloat endAngle = startAngle + (clockwise?(2*M_PI)*progress:(-2*M_PI)*progress);

    UIBezierPath *frontFillBezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(width/2.0f, width/2.0f) radius:(CGRectGetWidth(**self**.bounds)-**self**.trackWidth)/2.f startAngle:startAngle endAngle:endAngle clockwise:clockwise];

    _frontFillLayer.path = frontFillBezierPath.CGPath;
}

新的问题来了

  • button设置两种状态,nomal 和 selected 分别对应播放和暂停。暂停的时候进度条是不需要更新的。封装的原则就是让调用方尽可能简单的调用

解决

  • 内部听select的状态,直接写select的set方法好像也拿不到_select成员,那就直接KVO监听select的变动

-(**void**)observeValueForKeyPath:(NSString *)keyPath ofObject:(**id**)object change:(NSDictionary<NSKeyValueChangeKey,**id**> *)change context:(**void** *)context{

    **id** new = [change objectForKey:@"new"];

    **BOOL** selected = [new boolValue];

    **if** (selected) {

        [**self** setSelectedType];

    }**else**{

        [**self** setDeselectedType];

    }

}