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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

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