Animate colour of circle drawn using CGContextFill

2019-09-02 09:51发布

问题:

I have a UIView that draws a circle inside it using the following code:

-(void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(context, myColor);
    CGContextFillEllipseInRest(context, myRect);
}

I want to animate this circle to a grey color... How can I achieve this?

-(void)fadeToGrey {
    // What goes here?
}

回答1:

You should use CAShapeLayer as a layer class and do your drawing inside setLayerProperties: method. drawRect: will not be overridden. Here is my sample code with comments of custom UIView with attaching color animation method.

//Returns the class used to create the layer for instances of this class.
//it is CALayer class by default.
+ (Class)layerClass 
{
   return [CAShapeLayer class];
}

//Lays out subviews.
- (void)layoutSubviews
{
   [self setLayerProperties];
}

- (void)setLayerProperties {
//The view’s Core Animation layer used for rendering.
  CAShapeLayer *layer = (CAShapeLayer *)self.layer;
// Color Declarations
  UIColor* strokeColor = [UIColor redColor];
// Some sample Bezier Drawing. In my case it is a triangle.
// However, you can draw circle by using `+bezierPathWithRoundedRect:cornerRadius:`
   UIBezierPath* bezierPath = [UIBezierPath bezierPath];
   [bezierPath moveToPoint: CGPointMake(0.0, self.frame.size.height)];
   [bezierPath addLineToPoint: CGPointMake(self.frame.size.width/2, 0.0)];
   [bezierPath addLineToPoint: CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))];
   [bezierPath addLineToPoint: CGPointMake(0.0, CGRectGetHeight(self.frame))];
    bezierPath.miterLimit = 8;

    bezierPath.lineJoinStyle = kCGLineJoinBevel;
    layer.path = bezierPath.CGPath;
    layer.fillColor = strokeColor.CGColor;
}

//here is the magic :)
- (void)attachColorAnimationToColor:(UIColor *)color {

//"fillColor" property of CAShapeLayer's instance is animatable 
//(thus we have overridden `layerClass` method btw), that leads us to create 
//CABasicAnimation instance with fromValue (initial color), toValue (final color).

   CABasicAnimation *animation = [self animationWithKeyPath:@"fillColor"];
   animation.fromValue = (__bridge id)(((CAShapeLayer *)self.layer).fillColor);
   animation.toValue = (__bridge id)color.CGColor;
   [self.layer addAnimation:animation forKey:animation.keyPath];
}

//setting properties of color animation
- (CABasicAnimation *)animationWithKeyPath:(NSString *)keyPath {
   CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
   animation.fillMode = kCAFillModeForwards;
   animation.removedOnCompletion=NO;
   animation.repeatCount = 1;
   animation.duration = 0.3f;
   return animation;
 }

You will use it by calling attachColorAnimationToColor: like this:

//your custom view declaration
YourView *yourView = [[YourView alloc]initWithFrame:CGRectMake(...)];
[self.view addSubview:yourView];
[yourView attachColorAnimationToColor:[UIColor lightGrayColor]];

CAShape Layer Class Reference to fillColor property

Feel free to ask if something goes wrong.

UPDATE Our layer fillColor is red. So if you fire animation to redColor, that means that you want animation from 'red' to 'red'. Create method reverse animation, where toValue is our stroke color of shape. (Animation does not update it, it stays red always). Here is sample that was tested now :)

-(void)attachReverseAnimationFromColor:(UIColor*)color{
    CABasicAnimation *animation = [self animationWithKeyPath:@"fillColor"];
    animation.toValue = (__bridge id)(((CAShapeLayer *)self.layer).fillColor);
    animation.fromValue = (__bridge id)color.CGColor;
    [self.layer addAnimation:animation forKey:animation.keyPath];

}

PS Happy Coding.