iOS 弧形渐变色进度条

最近产品需求要搞个弧形进度条, 研究了一下仅供参考.

2019-09-06 14-53-31.2019-09-06 14_55_17.gif

核心代码

// ----- CTArcProgressView .h
@interface CTArcProgressView : UIView
@property (nonatomic, assign) CGFloat oneProgress;
@property (nonatomic, assign) CGFloat twoProgress;
@end

// ----- CTArcProgressView .m
@interface CTArcProgressView ()
// 背景
@property (nonatomic, strong) CAShapeLayer *bgShapeLayer;
// 渐变色
@property (nonatomic, strong) CAGradientLayer *gradientLayer;
// 进度
@property (nonatomic, strong) CAShapeLayer *progressOneLayer;
@property (nonatomic, strong) CAShapeLayer *progressTwoLayer;
// 进度圆点
@property (nonatomic, strong) CTArcProgressDotView *oneDotView;
@property (nonatomic, strong) CTArcProgressDotView *twoDotView;
// 进度描述
@property (nonatomic, strong) UILabel *oneNameLabel;
@property (nonatomic, strong) UILabel *twoNameLabel;

// 距离
@property (nonatomic, assign) CGFloat arcW;
@property (nonatomic, assign) CGFloat arcR;
@property (nonatomic, assign) CGFloat arcX;
@property (nonatomic, assign) CGFloat arcY;
@property (nonatomic, assign) CGFloat dotW;

@end


@implementation CTArcProgressView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self customView];
    }
    return self;
}

- (void)customView {
    self.arcW = 8.0;
    CGFloat r = KScreenWidth() > 375 ? 232 : 228;
    self.arcR = KAuToLayoutSize(r/2.0);
    self.arcX = (self.width - self.arcR*2 - self.arcW)/2;
    self.arcY = (self.height - self.arcR - self.arcW);
    self.dotW = 21.0;
    
    // 背景加阴影
    CAShapeLayer *shadowShaperLayer = [self creatShapeLayerWithStrokeEnd:1 strokeColor:Style.blackColor_96B6DA showShadow:YES startAngle:M_PI endAngle:M_PI*2 clockwise:YES];
    [self.layer addSublayer:shadowShaperLayer];
    
    // 背景色
    _bgShapeLayer = [self creatShapeLayerWithStrokeEnd:1 strokeColor:Style.blackColor_96B6DA showShadow:NO startAngle:M_PI endAngle:M_PI*2 clockwise:YES];
    // 进度1
    _progressOneLayer = [self creatShapeLayerWithStrokeEnd:0 strokeColor:[UIColor redColor] showShadow:NO startAngle:M_PI*1.5 endAngle:M_PI clockwise:NO];
    // 进度2
    _progressTwoLayer = [self creatShapeLayerWithStrokeEnd:0 strokeColor:[UIColor greenColor] showShadow:NO startAngle:M_PI*1.5 endAngle:M_PI*2 clockwise:YES];
    
    [self.layer addSublayer:_bgShapeLayer];
    [self.layer addSublayer:_progressOneLayer];
    [self.layer addSublayer:_progressTwoLayer];
    
    // 渐变图层映射到进度条路径上面  关键步骤
    _gradientLayer = [self creatGradientLayer];
    [_gradientLayer setMask:_bgShapeLayer];
    [self.layer addSublayer:_gradientLayer];
    
    [self addSubview:self.oneDotView];
    [self addSubview:self.twoDotView];
    
    [self addSubview:self.oneNameLabel];
    [self.oneNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.oneDotView.mas_left);
        make.bottom.equalTo(self.oneDotView.mas_top);
    }];
    
    [self addSubview:self.twoNameLabel];
    [self.twoNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.twoDotView.mas_right);
        make.bottom.equalTo(self.twoDotView.mas_top);
    }];
    
    self.oneProgress = 0.0;
    self.twoProgress = 0.0;
}

// 轨迹
- (CAShapeLayer *)creatShapeLayerWithStrokeEnd:(CGFloat)strokeEnd
                                   strokeColor:(UIColor *)strokeColor
                                    showShadow:(BOOL)showShadow
                                    startAngle:(CGFloat)startAngle
                                      endAngle:(CGFloat)endAngle
                                     clockwise:(BOOL)clockwise {
    
    UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width/2, self.height-self.arcW/2) radius:self.arcR startAngle:startAngle endAngle:endAngle clockwise:clockwise];
    
    CAShapeLayer *bgShapeLayer = [CAShapeLayer layer];
    bgShapeLayer.frame = self.bounds;
    bgShapeLayer.fillColor = [UIColor clearColor].CGColor;
    bgShapeLayer.lineWidth = self.arcW;
    bgShapeLayer.lineCap = kCALineCapRound;
    bgShapeLayer.lineJoin = kCALineJoinRound;
    bgShapeLayer.strokeColor = Style.blackColor_96B6DA.CGColor;
    bgShapeLayer.strokeStart = 0;
    bgShapeLayer.strokeEnd = strokeEnd;
    bgShapeLayer.path = circlePath.CGPath;
    
    if (showShadow) {
        bgShapeLayer.shadowColor = Style.orangeColor_A9591D.CGColor;
        bgShapeLayer.shadowOffset = CGSizeMake(0, 2);
        bgShapeLayer.shadowRadius = 5;
        bgShapeLayer.shadowOpacity = 0.3;
    }
    
    return bgShapeLayer;
}

// 渐变色
- (CAGradientLayer *)creatGradientLayer {
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.bounds;
    
    //左边渐变图层
    CAGradientLayer *leftGradientLayer = [CAGradientLayer layer];
    leftGradientLayer.frame = CGRectMake(self.arcX, self.arcY, self.arcR+self.arcW/2, self.arcR+self.arcW);
    [leftGradientLayer setColors:@[(id)Style.orangeColor_F3C174.CGColor, (id)Style.greenColor_5DC8A1.CGColor]];
    // 开始渐变色位置 0-1 为整个layer
    [leftGradientLayer setLocations:@[@0.2,@0.7]];
    // 渐变色方向
    [leftGradientLayer setStartPoint:CGPointMake(0, 1)];
    [leftGradientLayer setEndPoint:CGPointMake(1, 0)];
    [gradientLayer addSublayer:leftGradientLayer];
    
    // 右边渐变图层
    CAGradientLayer *rightGradientLayer = [CAGradientLayer layer];
    rightGradientLayer.frame = CGRectMake(self.arcX+self.arcR+self.arcW/2, self.arcY, self.arcR+self.arcW/2, self.arcR+self.arcW);
    [rightGradientLayer setColors:@[(id)Style.greenColor_5DC8A1.CGColor, (id)Style.orangeColor_F3C174.CGColor]];
    [rightGradientLayer setLocations:@[@0.3, @0.8]];
    [rightGradientLayer setStartPoint:CGPointMake(0, 0)];
    [rightGradientLayer setEndPoint:CGPointMake(1, 1)];
    [gradientLayer addSublayer:rightGradientLayer];
    return gradientLayer;
}

- (void)setOneProgress:(CGFloat)oneProgress {
    _oneProgress = oneProgress;
    
    // 是否安全区域
    if (_oneProgress < 0.3) {
        self.oneDotView.isSafe = YES;
    } else {
        self.oneDotView.isSafe = NO;
    }
    
    [self confineOneProgress];
    [self updateOneProgress];
}


- (void)setTwoProgress:(CGFloat)twoProgress {
    _twoProgress = twoProgress;
    
    if (twoProgress < 0.3) {
        self.twoDotView.isSafe = YES;
    } else {
        self.twoDotView.isSafe = NO;
    }
    
    [self confineTwoProgress];
    [self updateTwoProgress];
}

// 限制进度
- (void)confineOneProgress {
    if (_oneProgress < 0.05) _oneProgress = 0.05; // 防止重合
    if (_oneProgress > 1) _oneProgress = 1;
}

- (void)confineTwoProgress {
    if (_twoProgress < 0.05) _twoProgress = 0.05;
    if (_twoProgress > 1) _twoProgress = 1;
}

// 更新进度
- (void)updateOneProgress {
    _progressOneLayer.strokeEnd = _oneProgress;
    CGFloat angle = 90.0 + 90.0 * _oneProgress;
    CGPoint point = [self calcCircleCoordinateWithCenter:CGPointMake(self.width/2, self.height-self.arcW/2) andWithAngle:angle andWithRadius:self.arcR];
    self.oneDotView.center = point;
}

- (void)updateTwoProgress {
    _progressTwoLayer.strokeEnd = _twoProgress;
    CGFloat angle = 90.0 * (1 - _twoProgress);
    CGPoint point = [self calcCircleCoordinateWithCenter:CGPointMake(self.width/2, self.height-self.arcW/2) andWithAngle:angle andWithRadius:self.arcR];
    self.twoDotView.center = point;
}


- (CTArcProgressDotView *)oneDotView {
    if (!_oneDotView) {
        _oneDotView = [[CTArcProgressDotView alloc] initWithFrame:CGRectMake(self.arcX-self.dotW/4, self.height-self.dotW, self.dotW, self.dotW)];
        _oneDotView.isSafe = YES;
    }
    return _oneDotView;
}

- (CTArcProgressDotView *)twoDotView {
    if (!_twoDotView) {
        _twoDotView = [[CTArcProgressDotView alloc] initWithFrame:CGRectMake(self.arcX-self.dotW/4, self.height-self.dotW, self.dotW, self.dotW)];
        _oneDotView.isSafe = YES;
    }
    return _twoDotView;
}

- (UILabel *)oneNameLabel {
    if (!_oneNameLabel) {
        _oneNameLabel = [self creatNameLabelWithText:@"增值税"];
    }
    return _oneNameLabel;
}

- (UILabel *)twoNameLabel {
    if (!_twoNameLabel) {
        _twoNameLabel = [self creatNameLabelWithText:@"企业所得税"];
    }
    return _twoNameLabel;
}

- (UILabel *)creatNameLabelWithText:(NSString *)text {
    UILabel *nameLabel = [[UILabel alloc] init];
    nameLabel = [[UILabel alloc] init];
    nameLabel.textColor = Style.blackColor_414141;
    nameLabel.font = [UIFont systemFontOfSize:12];
    nameLabel.textAlignment = NSTextAlignmentCenter;
    nameLabel.text = text;
    [nameLabel sizeToFit];
    return nameLabel;
}



/**
 @param center 中心点
 @param angle 角度 0-360
 @param radius 半径
 @return 圆上坐标
 */
- (CGPoint)calcCircleCoordinateWithCenter:(CGPoint)center  andWithAngle:(CGFloat)angle andWithRadius:(CGFloat)radius{
    CGFloat x2 = radius*cosf(angle*M_PI/180);
    CGFloat y2 = radius*sinf(angle*M_PI/180);
    return CGPointMake(center.x+x2, center.y-y2);
}

/*
 --------------------------------------------------------------
 功能说明
 --------------------------------------------------------------
 根据IOS视图中圆组件的中心点(x,y)、半径(r)、圆周上某一点与圆心的角度这3个
 条件来计算出该圆周某一点在IOS中的坐标(x2,y2)。
 
 注意:
 (1)IOS坐标体系与数学坐标体系有差别,因此不能完全采用数学计算公式。
 (2)数学计算公式:
 x2=x+r*cos(角度值*PI/180)
 y2=y+r*sin(角度值*PI/180)
 (3)IOS中计算公式:
 x2=x+r*cos(角度值*PI/180)
 y2=y-r*sin(角度值*PI/180)
 
 --------------------------------------------------------------
 参数说明
 --------------------------------------------------------------
 @param (CGPoint) center
 
 圆圈在IOS视图中的中心坐标,即该圆视图的center属性
 
 @param (CGFloat) angle
 角度值,是0~360之间的值。
 注意:
 (1)请使用下面坐标图形进行理解。
 (2)角度是逆时针转的,从x轴中心(0,0)往右是0度角(或360度角),往左是180度角,往上是90度角,往下是270度角。
 
       (y)90
        ^
        |
        |
        |
        |
180 -----------------> (x)0/360
        |(0,0)
        |
        |
        |
        270
 @param (CGFloat) radius
 圆周半径
 */

@end

圆点
// 进度圆点
@interface CTArcProgressDotView : UIView
@property (nonatomic,strong) UIView *centerView;
@property (nonatomic,assign) BOOL isSafe; // 是否安全 YES绿色 NO黄色
@end

@implementation CTArcProgressDotView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self customView];
    }
    return self;
}

- (void)setIsSafe:(BOOL)isSafe {
    _isSafe = isSafe;
    UIColor *safeCenterColor = Style.greenColor_5DC8A1;
    UIColor *safeBgColor = [Style.greenColor_DEF1D6 colorWithAlphaComponent:0.8];
    UIColor *noSafeCenterColor = Style.orangeColor_F2BC68;
    UIColor *noSafeBgColor = [Style.orangeColor_FFECCE colorWithAlphaComponent:0.8];
    if (isSafe) {
        self.centerView.backgroundColor = safeCenterColor;
        self.backgroundColor = safeBgColor;
        self.layer.shadowColor = safeCenterColor.CGColor;
    } else {
        self.centerView.backgroundColor = noSafeCenterColor;
        self.backgroundColor = noSafeBgColor;
        self.layer.shadowColor = noSafeCenterColor.CGColor;
    }
}


- (void)customView {
    self.layer.cornerRadius = self.width/2.0;
    self.layer.shadowColor = Style.blackColor_96B6DA.CGColor;
    self.layer.shadowOffset = CGSizeMake(0, 2);
    self.layer.shadowRadius = 5;
    self.layer.shadowOpacity = 0.5;
    
    self.centerView.backgroundColor = [UIColor redColor];
    self.centerView.layer.cornerRadius = (self.width-8)/2.0;
    [self addSubview:self.centerView];
    [self.centerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);
        make.width.equalTo(@(self.width-8));
        make.height.equalTo(@(self.height-8));
    }];
}

- (UIView *)centerView {
    if (!_centerView) {
        _centerView = [[UIView alloc] init];
    }
    return _centerView;
}

@end

推荐阅读更多精彩内容

  • 最近在开发的时候遇到了一个环形进度条的需求,设计师希望这个进度条是渐变色的,并且能有对应的动画。具体效果如图 下面...
    aboyl阅读 2,422评论 0 11
  • 案例地址http://www.atynote.com/webstudy/锚链接.html当在同一个页面点击菜单的导...
    WangYatao阅读 130评论 0 0
  • Flutter Widget 采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用 Widget...
    iwakevin阅读 1,141评论 0 2
  • 我不知道你是否有过觉得未来迷茫不知何去何从的时候,是否有过直到深夜还无法安然入睡的时候,是否有过想尽一切办法哄自己...
    Dana懒懒阅读 156评论 0 0