Animate CALayer custom properties with CoreAnimation

本文介绍如何通过Core Animation框架实现自定义图层(CALayer)的属性动画,如圆的半径和边框宽度等。文章详细解释了自定义图层的创建过程,并演示了如何设置动画关键路径来实现平滑过渡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.nomadplanet.fr/2010/11/animate-calayer-custom-properties-with-coreanimation/


Animate CALayer custom properties with CoreAnimation

Apple’s Core Animation framework is a powerful way for developpers to produce animated content in their graphical interfaces. It is simple enough so that the developer only needs to specify the initial and final states of an object in order to make it animate. Core Animation handles interpolation of intermediate values and executes the animation in different thread than the main run loop, the developper doesn’t need to write specific code for the animations. And CoreAnimations can be automagically accelerated by the GPU.

CoreAnimation can be used in several ways, from the high-level UIView animation syntax down to implicit and explicit CAAnimations.
All animations finally end up being animations of CALayers properties. There are a number of CALayer properties that can be animated, they are called the animatable-properties by Apple (opacity, bounds, content, cornerRadius etc…)

However in some cases, the developper may need to implement its own CALayer subclass. The main purpose of doing such a thing is to let the developer draw custom content in the drawInContext: method (a bit like the drawRect: UIView method).

Let’s say you have a custom CALayer, which basically draws a circle with a given radius. The circle have a border on which you can control the thickness. Let’s name that class CircleLayer.

Basically its interface would look like this:

1 #import <Foundation/Foundation.h>
2 #import <QuartzCore/QuartzCore.h>
3  
4 @interface CircleLayer : CALayer {
5     CGFloat     radius;
6     CGFloat     strokeWidth;
7 }
8  
9 @property (nonatomic, assign) CGFloat   radius;
10 @property (nonatomic, assign) CGFloat   strokeWidth;
11  
12 @end

and in CircleLayer.m

1 - (void)drawInContext:(CGContextRef)ctx {
2     NSLog(@"Drawing layer, strokeWidth is %f, radius is %f"self.strokeWidth,self.radius);
3  
4     CGPoint centerPoint = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2);
5  
6     /* Path the circle */
7     CGContextAddArc(ctx, centerPoint.x, centerPoint.y, self.radius, 0.0, 2*M_PI, 0);
8     CGContextClosePath(ctx);
9  
10     /* And fill it */
11     CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
12     CGContextFillPath(ctx);
13  
14     /* Path the circle again */
15     CGContextAddArc(ctx, centerPoint.x, centerPoint.y, self.radius, 0.0, 2*M_PI, 0);
16     CGContextClosePath(ctx);
17  
18     /* Stroke the path */
19     CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
20     CGContextSetLineWidth(ctx, self.strokeWidth);
21     CGContextStrokePath(ctx);
22 }

If the developper want to make the radius and/or the stroke width of the circle to animate, he would implement a CAAnimation with a fromValue and toValue:

1 - (void) animateRadius {
2     CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"radius"];
3     anim.duration = 3.0;
4     anim.fromValue = [NSNumber numberWithDouble:50.0];
5     anim.toValue = [NSNumber numberWithDouble:150.0];
6     anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
7  
8     [circleLayer addAnimation:anim forKey:@"animateRadius"];
9  
10     circleLayer.radius = 150.0;
11 }
12  
13 - (void) animateStrokeWidth {
14     CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeWidth"];
15     anim.duration = 4.0;
16     anim.fromValue = [NSNumber numberWithDouble:25.0];
17     anim.toValue = [NSNumber numberWithDouble:1.0];
18     anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
19  
20     [circleLayer addAnimation:anim forKey:@"animateStrokeWidth"];
21  
22     circleLayer.strokeWidth = 1.0;
23 }

However these two methods (which would be placed for instance in the UIView hosting the circleLayer) attempt to animate radius and strokeWidth which are not part of CoreAnimation’s animatable properties. So they wont animate.

But there is a solution, which is not really clear to me in Apple’s documentation. CircleLayer.m needs to implement two other methods:

The first, needsDisplayForKey: tells CoreAnimation which properties of the layer causes the layer to be marked as ‘dirty’ (i.e: needs to be redrawn by drawInContext: method).

1 + (BOOL)needsDisplayForKey:(NSString *)key {
2     if ([key isEqualToString:@"radius"]
3         || [key isEqualToString:@"strokeWidth"]) {
4         return YES;
5     }
6     else {
7         return [super needsDisplayForKey:key];
8     }
9 }

So here we tell that an update on radius or on strokeWidth should cause the layer to be redrawn (other properties cause the redraw too, with the help of [super needsDisplayForKey:key]).

The second, initWithLayer: will ensure that custom properties will be copied for presentation layers. When you animate a CALayer, it creates presentation copies of the layer (using initWithLayer:) for each and every frame of the animation. Each presentation layer contains an intermediate value of the animated properties. The original layer contains the final state of the properties.

1 - (id) initWithLayer:(id)layer {
2     if(self = [super initWithLayer:layer]) {
3         if([layer isKindOfClass:[CircleLayer class]]) {
4             CircleLayer *other = (CircleLayer*)layer;
5             self.radius = other.radius;
6             self.strokeWidth = other.strokeWidth;
7         }
8     }
9     return self;
10 }

Since our custom properties are not copied automatically by CALayer, not implementing initWithLayer: would result inradius and strokeWidth to be zeroed for each intermediate frame, and then finally jumping to their last value when the last frame is displayed.

The radius and border thickness currently being animated

Now you have a simple solution to animate whatever kind of properties on your custom CALayer as long as they represent numbers (which can be interpolated). And yes it works with NSNumbers too.

[UPDATE]: You can find a demo project in this article.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值