Animated CAShapeLayer Pie

后端 未结 4 2036
遥遥无期
遥遥无期 2020-12-13 16:16

I am trying to create a simple and animated pie chart using CAShapeLayer. I want it to animate from 0 to a provided percentage.

To create the shape laye

4条回答
  •  被撕碎了的回忆
    2020-12-13 17:13

    I know this question has long been answered, but I don't quite think this is a good case for CAShapeLayer and CAKeyframeAnimation. Core Animation has the power to do animation tweening for us. Here's a class (with a wrapping UIView, if you like) that I use to accomplish the effect pretty well.

    The layer subclass enables implicit animation for the progress property, but the view class wraps its setter in a UIView animation method. The interesting (and ultimately useful) side effect of using a 0.0 length animation with UIViewAnimationOptionBeginFromCurrentState is that each animation cancels out the previous one, leading to a smooth, fast, high-framerate pie, like this (animated) and this (not animated, but incremented).

    DZRoundProgressView.h

    @interface DZRoundProgressLayer : CALayer
    
    @property (nonatomic) CGFloat progress;
    
    @end
    
    @interface DZRoundProgressView : UIView
    
    @property (nonatomic) CGFloat progress;
    
    @end
    

    DZRoundProgressView.m

    #import "DZRoundProgressView.h"
    #import 
    
    @implementation DZRoundProgressLayer
    
    // Using Core Animation's generated properties allows
    // it to do tweening for us.
    @dynamic progress;
    
    // This is the core of what does animation for us. It
    // tells CoreAnimation that it needs to redisplay on
    // each new value of progress, including tweened ones.
    + (BOOL)needsDisplayForKey:(NSString *)key {
        return [key isEqualToString:@"progress"] || [super needsDisplayForKey:key];
    }
    
    // This is the other crucial half to tweening.
    // The animation we return is compatible with that
    // used by UIView, but it also enables implicit
    // filling-up-the-pie animations.
    - (id)actionForKey:(NSString *) aKey {
        if ([aKey isEqualToString:@"progress"]) {
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:aKey];
            animation.fromValue = [self.presentationLayer valueForKey:aKey];
            return animation;
        }
        return [super actionForKey:aKey];
    }
    
    // This is the gold; the drawing of the pie itself.
    // In this code, it draws in a "HUD"-y style, using
    // the same color to fill as the border.
    - (void)drawInContext:(CGContextRef)context {
        CGRect circleRect = CGRectInset(self.bounds, 1, 1);
    
        CGColorRef borderColor = [[UIColor whiteColor] CGColor];
        CGColorRef backgroundColor = [[UIColor colorWithWhite: 1.0 alpha: 0.15] CGColor];
    
        CGContextSetFillColorWithColor(context, backgroundColor);
        CGContextSetStrokeColorWithColor(context, borderColor);
        CGContextSetLineWidth(context, 2.0f);
    
        CGContextFillEllipseInRect(context, circleRect);
        CGContextStrokeEllipseInRect(context, circleRect);
    
        CGFloat radius = MIN(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
        CGPoint center = CGPointMake(radius, CGRectGetMidY(circleRect));
        CGFloat startAngle = -M_PI / 2;
        CGFloat endAngle = self.progress * 2 * M_PI + startAngle;
        CGContextSetFillColorWithColor(context, borderColor);
        CGContextMoveToPoint(context, center.x, center.y);
        CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
        CGContextClosePath(context);
        CGContextFillPath(context);
    
        [super drawInContext:context];
    }
    
    @end
    
    @implementation DZRoundProgressView
    + (Class)layerClass {
        return [DZRoundProgressLayer class];
    }
    
    - (id)init {
        return [self initWithFrame:CGRectMake(0.0f, 0.0f, 37.0f, 37.0f)];
    }
    
    - (id)initWithFrame:(CGRect)frame {
        if ((self = [super initWithFrame:frame])) {
            self.opaque = NO;
            self.layer.contentsScale = [[UIScreen mainScreen] scale];
            [self.layer setNeedsDisplay];
        }
        return self;
    }
    
    - (void)setProgress:(CGFloat)progress {
        [(id)self.layer setProgress:progress];
    }
    
    - (CGFloat)progress {
        return [(id)self.layer progress];
    }
    
    @end
    

提交回复
热议问题