封装环形进度条

功能:

1. 可改变 背景/中心图标/文字描述 (此进度图显示的刻度为背景图片)
2. 绘制进度时有动画和非动画2种模式,并显示当前value值

效果.gif

1. 创建FYCircleView继承UIView

FYCircleView.h:

@interface FYCircleView : UIView

@property int minNum;
@property int maxNum;

@property NSString *units;

@property(nonatomic,strong) NSString *backImageName;
@property(nonatomic,strong) NSString *iconName;
@property(nonatomic,strong) NSString *meaningStr;
//进度 [0...1]
@property(nonatomic,assign) CGFloat lastProgress;
@property(nonatomic,assign) CGFloat progress;

@property int flag;

@property (nonatomic,copy) void (^progressChange)(NSString *result,int flag);

- (void)updateProgress:(CGFloat)progress animation:(BOOL)animationed color:(UIColor *)color value:(int)value;

@end

FYCircleView.m:

#define DEGREES_TO_RADIANS(degrees)  ((M_PI * degrees)/ 180)
#define CATProgressStartAngle     (-220)   // 进度圆环起始角度
#define CATProgressEndAngle       (40)     // 进度圆环结束角度

// 3种颜色
#define FYEnvGreenColor UIColorFromRGB(0x00e65c)
#define FYEnvYellowColor UIColorFromRGB(0xffff00)
#define FYEnvRedColor UIColorFromRGB(0xff6462)

@interface JKCircleView () <UIGestureRecognizerDelegate,CAAnimationDelegate>

// 进度圆环
@property CGFloat     dialRadius;

// 背景圆环
@property CGFloat     outerRadius;  // 不要设置除非需要方形页面
@property CGFloat     arcRadius;    // clipsToBounds时必须<outerRadius
@property CGFloat     arcThickness; // 圆环宽度
@property CGPoint     trueCenter;
@property UILabel     *numberLabel;
@property UILabel     *meaningLabel;
@property UIImageView *iconImage;
@property UIImageView *backImage;

@property int         currentNum;
@property double      angle;

@property (nonatomic, strong) CAShapeLayer *trackLayer;
@property (nonatomic, strong) CAShapeLayer *progressLayer;

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation JKCircleView

- (id) initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if(self){
        
        self.userInteractionEnabled = YES;
        self.clipsToBounds = YES;
        self.backgroundColor = [UIColor clearColor];
        
        // 设置默认值
        self.minNum = 0;
        self.maxNum = 100;
        self.currentNum = self.minNum;
        self.units = @"";
        self.iconName = @"温度";
        self.backImageName = @"背景";
        self.lastProgress = 0.0;
        
        CGFloat width = frame.size.width;
        CGFloat height = frame.size.height;
        self.trueCenter = CGPointMake(width/2, height/2);
        
        // 设置radius
        self.dialRadius = 10;
        self.arcRadius = 80;
        self.outerRadius = MIN(width, height)/2;
        self.arcThickness = 8.0;
        
        // 背景图片
        self.backImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, width, height-30)];
        self.backImage.image = [UIImage imageNamed:self.backImageName];
        [self addSubview:self.backImage];
        
        // 中心数值
        self.numberLabel = [[UILabel alloc] initWithFrame:CGRectMake(width*.1, height/2 - width/6, 85, 25)];
        self.numberLabel.text = [NSString stringWithFormat:@"%d", self.currentNum];
        self.numberLabel.center = CGPointMake(self.trueCenter.x, self.trueCenter.y+20);
        self.numberLabel.textAlignment = NSTextAlignmentCenter;
        self.numberLabel.font = [UIFont systemFontOfSize:16];
        self.numberLabel.textColor = [UIColor whiteColor];
        [self addSubview:self.numberLabel];
        
        // 中心图标
        self.iconImage = [[UIImageView alloc] initWithFrame:CGRectMake(width*.1, height/2 - width/6, 35, 35)];
        self.iconImage.center = CGPointMake(self.trueCenter.x, self.trueCenter.y - 10);
        self.iconImage.image = [UIImage imageNamed:self.iconName];
        [self addSubview:self.iconImage];

        // 下方内容
        self.meaningLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, height - 20, width, 20)];
        self.meaningLabel.text = [NSString stringWithFormat:@"%@",self.meaningStr];
        self.meaningLabel.textAlignment = NSTextAlignmentCenter;
        self.meaningLabel.font = [UIFont systemFontOfSize:14];
        self.meaningLabel.textColor = [UIColor whiteColor];
        [self addSubview:self.meaningLabel];
        
        // 背景轨道
        _trackLayer=[CAShapeLayer layer];
        _trackLayer.frame = self.bounds;
        _trackLayer.fillColor = [UIColor clearColor].CGColor;
        _trackLayer.strokeColor = [UIColor whiteColor].CGColor;
        _trackLayer.opacity = 0.25;   //背景圆环的背景透明度
        _trackLayer.lineCap = kCALineCapRound;
        [self.layer addSublayer:_trackLayer];
        
        self.arcRadius = MIN(self.arcRadius, self.outerRadius - self.dialRadius);
        UIBezierPath *path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:self.arcRadius startAngle:DEGREES_TO_RADIANS(CATProgressStartAngle) endAngle:DEGREES_TO_RADIANS(CATProgressEndAngle) clockwise:YES];//-210到30的path
        _trackLayer.path = path.CGPath;
        _trackLayer.lineWidth = self.arcThickness;
        
        // 进度轨道
        _progressLayer = [CAShapeLayer layer];
        _progressLayer.frame = self.bounds;
        _progressLayer.fillColor = [[UIColor clearColor] CGColor];
        _progressLayer.strokeColor = FYEnvGreenColor.CGColor; //!不能用clearColor
        _progressLayer.lineCap=kCALineCapRound;
        _progressLayer.strokeEnd = 0.0;
        [self.layer addSublayer:_progressLayer];
        
        self.arcRadius = MIN(self.arcRadius, self.outerRadius - self.dialRadius);
        CGFloat start = CATProgressStartAngle;
        CGFloat end = CATProgressEndAngle;
        UIBezierPath *path1=[UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:self.arcRadius startAngle:DEGREES_TO_RADIANS(start) endAngle:DEGREES_TO_RADIANS(end) clockwise:YES];//-210到30的path
        
        _progressLayer.path = path1.CGPath;
        _progressLayer.lineWidth = self.arcThickness;
    
    }
    return self;
}

- (void)setProgress:(CGFloat)progress {
    _progress = progress;
    
    if (progress < 0.0) _progress = 0.0;
    if (progress > 1.0) _progress = 1.0;
}

- (void)updateProgress:(CGFloat)progress animation:(BOOL)animationed color:(UIColor *)color value:(int)value{
    
    self.progress = progress;
    [self.progressLayer removeAllAnimations];
    
    // 非动画形式
    if (!animationed) {
        
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
        [CATransaction setAnimationDuration:0.5];
        
        self.progressLayer.strokeEnd = self.progress;

        self.currentNum = self.minNum + (self.maxNum - self.minNum)*progress;
        self.numberLabel.text = [NSString stringWithFormat:@"%d", self.currentNum];
        if (self.progressChange) {
            self.progressChange([NSString stringWithFormat:@"%d",self.currentNum],self.flag);
        }
        [CATransaction commit];
        
    //动画形式
    } else {
        
        CABasicAnimation *animation= [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        animation.fromValue = @(self.lastProgress);
        animation.toValue = @(progress);
        animation.duration = 1.5 * self.progress;
        animation.removedOnCompletion = YES;
        animation.delegate = self;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
        
        _progressLayer.strokeColor = color.CGColor;
        
        self.progressLayer.strokeEnd = self.progress;
        
        [self.progressLayer addAnimation:animation forKey:@"strokeEndAnimation"];

        self.currentNum = value;
        self.numberLabel.text = [NSString stringWithFormat:@"%d", self.currentNum];
        if (self.progressChange) {
            self.progressChange([NSString stringWithFormat:@"%d",self.currentNum],self.flag);
        }
 
    }
    self.lastProgress = progress;
}

- (void) willMoveToSuperview:(UIView *)newSuperview{
    [super willMoveToSuperview:newSuperview];
    
    self.arcRadius = MIN(self.arcRadius, self.outerRadius - self.dialRadius);

    self.numberLabel.text = [NSString stringWithFormat:@"%d", self.currentNum];
    self.meaningLabel.text = [NSString stringWithFormat:@"%@",self.meaningStr];
    self.backImage.image = [UIImage imageNamed:self.backImageName];
    self.iconImage.image = [UIImage imageNamed:self.iconName];
}

2. 创建view

    //背景图片
    NSArray *backImageArr = @[@"temperature_back",
                              @"wet_back",
                              @"posionGas_back",
                              @"carbonDioxide_back",
                              @"PM2.5_back",
                              @"noise_back"];
    //图标
    NSArray *imageArr = @[@"temperature_icon",
                          @"wet_icon",
                          @"posionGas_icon",
                          @"carbonDioxide_icon",
                          @"PM2.5_icon",
                          @"noise_icon"];
    //value单位,此处并没有应用
    NSArray *tipArr = @[@"℃",@"m³/h",@"%",@"%",@"%",@"%"];
    //下方描述
    NSArray *meanLabelArr = @[@"温度(℃)", @"湿度(%RH)", @"有害气体(PPM)", @"二氧化碳(%)", @"PM2.5(MG/M³)", @"噪音(DB)"];
    
    CGFloat W = FYProgressLoop_W;
    CGFloat H = FYProgressLoop_H;
    CGFloat marginX = (SCREEN_WIDTH - W*2 - FYViewMagin*2)/2;
    
    for (int i = 0; i < 6; i++) {
        
        int col_idx = i % 2;  //列索引
        int row_idx = i / 2;  //行索引
        CGFloat X = FYViewMagin + col_idx * (W + 2*marginX);
        CGFloat Y = 60 + row_idx * (H + 20);
        JKCircleView *dialView = [[JKCircleView alloc] initWithFrame:CGRectMake(X, Y, W, H)];
        
        //取值范围:最大值、最小值
        dialView.minNum = 0;
        if (i == 0) dialView.maxNum = 35;
        if (i == 1) dialView.maxNum = 100;
        if (i == 2) dialView.maxNum = 180;
        if (i == 3) dialView.maxNum = 1500;
        if (i == 4) dialView.maxNum = 100;
        if (i == 5) dialView.maxNum = 150;
        
        dialView.flag = i+100;
        dialView.tag = i+100;
        dialView.units = tipArr[i];                  //单位名称
        dialView.backImageName = backImageArr[i];    //背景图片
        dialView.iconName = imageArr[i];             //中间图标
        dialView.progress = 0.0;
        dialView.meaningStr = meanLabelArr[i];
        
        [self.view addSubview:dialView];
    }

3. 赋值

    //测试数据
    wendu1 = [self getRandomNumberFrom:15 to:35];
    wet2 = 36;
    posionGas3 = [self getRandomNumberFrom:20 to:130];
    eyht4 = [self getRandomNumberFrom:200 to:1200];
    PM5 = [self getRandomNumberFrom:0 to:80];
    noise6 = [self getRandomNumberFrom:0 to:90];

    // 注意:此f值为测试出来的误差值所做的修复,当进度超过50%不会有误差,小于50%时会有。
    CGFloat f = 0.03;
    for (int i = 0; i < 6; i++) {
        JKCircleView *dialView = [self.view viewWithTag:100+i];
        switch (i) {
            case 0:
                [dialView updateProgress:(wendu1*1.0/35>0.5||wendu1*1.0/35<f ? wendu1*1.0/35 : wendu1*1.0/35-f)animation:YES color:_wenduColor value:wendu1];
                break;
            case 1:
                [dialView updateProgress:(wet2*1.0/100>0.5||wet2*1.0/100<f ? wet2*1.0/100 : wet2*1.0/100-f) animation:YES color:_wetColor value:wet2];
                break;
            case 2:
                [dialView updateProgress:(posionGas3*1.0/180>0.5||posionGas3*1.0/180<f ? posionGas3*1.0/180 : posionGas3*1.0/180-f) animation:YES color:_posionGasColor value:posionGas3];
                break;
             case 3:
                [dialView updateProgress:(eyht4*1.0/1500>0.5||eyht4*1.0/1500<f ? eyht4*1.0/1500 : eyht4*1.0/1500-f) animation:YES color:_eyhtColor value:eyht4];
                break;
            case 4:
                [dialView updateProgress:(PM5*1.0/100>0.5||PM5*1.0/100>0.5<f ? PM5*1.0/100 : PM5*1.0/100-f) animation:YES color:_PMColor value:PM5];
                break;
            case 5:
                [dialView updateProgress:(noise6*1.0/150>0.5||noise6*1.0/150<f ? noise6*1.0/150 : noise6*1.0/150-f) animation:YES color:_noiseColor value:noise6];
                break;
            default:
                break;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,103评论 4 62
  • 无题
    丹紫酱阅读 142评论 0 0
  • 人生没有事事如意的时候,却有事事都不如意的时候
    小柴胡粉丝阅读 180评论 0 0