环形进度条的简单实现

来源:互联网 发布:淘宝专业术语大全 编辑:程序博客网 时间:2024/06/01 10:23
小牢骚

好久没有记录文章了,近期在学习前端的相关知识,也没有好好的静下心来整理东西,这次准备把之前自己用到的东西做一个总结,方便以后自己查阅.

正文

这次记录使用CALayer创建一个环形进度条的view,加上简单的动画,项目中刚好有使用到。
效果图:
porgressView.gif
要点: 主要使用layer的mask属性,mask属性可以让layer只显示遮罩层上面的路径(环形)

分析

在进度值为0的时候,有一个黄色的底色,然后进度条的颜色是渐变的,底色环形可以通过给view的layer添加一个带环形路径的CAShapeLayer作为mask,渐变色可以使用CAGrandientLayer创建一个渐变图层,然后创建一个带环形路径的CAShapeLayer,将其作为渐变图层的mask,这样就能达到预期的效果

相关知识点

  • CAGradientLayer
    创建渐变色layer,他是继承自CALayer的一个用于绘制多色图层的子类,主要用于设置的属性值为
    • @property(nullable, copy) NSArray *colors;
      设置需要加载的颜色数组(CGColorRef类型)
    • @property(nullable, copy) NSArray
#define DEGREES_TO_ANGLE(x) (M_PI * (x) / 180.0) // 将角度转为弧度CAShapeLayer *layer = [CAShapeLayer layer];    layer.frame = self.bounds;    UIBezierPath *path =  [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2) radius:self.bounds.size.width / 2.5 startAngle:DEGREES_TO_ANGLE(-90) endAngle:DEGREES_TO_ANGLE(270) clockwise:YES]; //注意这里的半径,应该为宽度1/2-linewidth/2    layer.lineWidth = self.lineWidth; //线的宽度    layer.path = path.CGPath;    layer.fillColor = [UIColor clearColor].CGColor; //设置填充颜色    layer.strokeColor = [UIColor blackColor].CGColor; // 设置画笔颜色    layer.lineCap = kCALineCapRound; // 设置线为圆角

其中需要注意的点:
- 这里的坐标系和数学中的坐标系是反的,简单的理解就是-90°等价数学坐标系中的90°,90°等价数学坐标系中的270°,所以如果希望进度条从顶部按照顺时针走,那么在设置UIBezierPath画圆弧的时候就应该为clockwise:yes(顺时针),startangle(开始弧度):(-90°对应的弧度),endangle:(270°对应的弧度),这样就是生成按照顺时针方向从-90°的点到270°的点之间的路径。
如下图片中不同的角度对应的效果图:
多种弧度的圆弧.png
- 在选择半径时,如果fram宽度为100,希望绘制一个宽度20紧靠边缘的环形,那么半径应该是50-20/2,并不是50-20。

代码解析

progressView.h

typedef NS_ENUM (NSInteger, WJStrokeType)//动画方式{    WJStrokeTypeStart, //开始    WJStrokeTypeEnd //结束};//动画结束回调typedef void(^ProgressEnd)();@interface WJProgressView : UIView/** *  线宽 */@property(nonatomic,assign)CGFloat lineWidth;/** *  背景颜色 */@property(nonatomic,strong)UIColor *bgColor;/** *  渐变色颜色组 */@property(nonatomic,strong)NSArray *colors;/** *  颜色分割位置数组 */@property(nonatomic,strong)NSArray *locations;/** *  隐藏百分比label */- (void)hidePercent;/** *  设置创建图层 */- (void)startProress;/** *  更新progress * *  @param value progress Value */- (void)reloadValue:(CGFloat)value;/** *  进度动画 * *  @param beginValue 开始value *  @param endValue   结束value *  @param duration   动画持续时间 *  @param type       动画类型 *  @param endBlock   动画结束回调 */- (void)setprogressFromValue:(CGFloat)beginValue endValue:(CGFloat)endValue duration:(CGFloat)duration strokeType:(WJStrokeType)type end:(ProgressEnd)endBlock;

.m

- (void)setprogressFromValue:(CGFloat)beginValue endValue:(CGFloat)endValue duration:(CGFloat)time strokeType:(WJStrokeType)type end:(ProgressEnd)endBlock{    if (beginValue >= 1.0) {        endBlock();        return;    }    _endValue = endValue;    self.animationEnd = endBlock;    NSString *animationName = @"strokeStart";    if (type) {        animationName = @"strokeEnd";    }    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:animationName];    pathAnimation.duration = time;    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];    pathAnimation.fromValue = [NSNumber numberWithFloat:beginValue];    pathAnimation.toValue = [NSNumber numberWithFloat:endValue];    pathAnimation.autoreverses = NO;    pathAnimation.delegate = self;    pathAnimation.removedOnCompletion = NO;    pathAnimation.fillMode = kCAFillModeForwards;    [self.colorMaskLayer addAnimation:pathAnimation forKey:@"strokeAnimation"];    [self creatTimerFromValue:beginValue endValue:endValue duration:time];}

对于cCAShapeLayer,stokeEnd和strokestart属性都直接动画操作。
在动画过程中关于中间percentLabel的显示,使用定时器设置时间间隔实现变化。

/** 创建定时器在layer animation过程中改变percent @param beginValue 初始值 @param endValue 结束值 @param time 时间间隔 */- (void)creatTimerFromValue:(CGFloat)beginValue endValue:(CGFloat)endValue duration:(CGFloat)time{    __block CGFloat beginPercent = beginValue;    CGFloat timeInterval = 0.1;    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);    dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), timeInterval*NSEC_PER_SEC, 0);    dispatch_source_set_event_handler(self.timer, ^{        dispatch_async(dispatch_get_main_queue(), ^{            CGFloat difference = endValue - beginValue;            CGFloat addValue = difference/time/(1/timeInterval);            beginPercent += addValue;            if (beginPercent >= endValue)  beginPercent = endValue;            if (beginPercent >= 1.0)  beginPercent = 1.0f;            [self reloadPercentLabelValue:beginPercent];        });    });    dispatch_resume(_timer);}

注意点:dispatch_source_t 的创建定一个属性值来保存。不要直接在方法中创建。
在动画结束后对timer进行销毁处理。

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{    if (self.animationEnd) self.animationEnd();    [self.colorMaskLayer removeAnimationForKey:@"strokeAnimation"];    self.colorMaskLayer.strokeEnd = _endValue;    if (self.timer) {        dispatch_source_cancel(self.timer);        self.timer = nil;    }}

End demo地址

原创粉丝点击