环形进度条的简单实现

小牢骚

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

正文

这次记录使用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地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值