美化图层
CALayer相对于UIView有一个主要优点,即便你工作在2D环境中,CALayer也支持自动边框效果。比如说,CALayer可以自动生成圆角、彩色边线以及阴影。所有这些都可应用动画效果,可以提供非常好的视觉体验。举个例子,你可以在用户点击并释放图层时出发更改位置和阴影的动画效果
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100,100,100,100);
layer.cornerRadius = 10;
layer.backgroundColor = [[UIColor redColor] CGColor];
layer.borderColor = [[UIColor blueColor] CGColor];
layer.borderWidth = 5;
layer.shadowOpacity = 0.5;
layer.shadowOffset = CGSizeMake(3.0,3.0);
[self.view.layer addSublayer:layer];
用动作实现自动动画
隐式动画大多数情况下能达到你的要求,不过有时你还要配置它们。可以通过CATransaction关闭所有隐式动画,不过这只对当前事务(通常就是当前的运行循环)有效。如果要修改隐式动画行为,尤其是想让它针对该图层一直保持这种行为,就需要配置图层的动作。这样,可以在创建图层的时候就配置动画,而不需要每次更改一个属性都应用一个显式动画。
图层动作会相应图层上的各种变化,比如添加或移除图层或者修改某个属性。丽日,假设修改position属性,默认动作是执行动画的0.25秒。在下面的代码中,CirecleLayer是一个在中间依据指定的radius(半径)绘制红色圆圈的图层。
CircleLayer *circleLayer = [CircleLayer new];
circleLayer.radius = 20;
circleLayer.frame = self.view.bounds;
[self.view.layer addSubLayer:circleLayer];
...
[circleLayer setPosition:CGPointMake(100,100)];
//我们来修改它,以使更改位置时动画一直是2秒
CircleLayer *circleLayer = [CircleLayer new];
circleLayer.radius = 20;
circleLayer.frame = self.view.bounds;
[self.view.layer addSubLayer:circleLayer];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
anim.duration = 2;
NSMutableDictionary *actions = [NSMutableDictionary dictionaryWithDictionary:[circleLayer actions]];
[actions setObject:anim forKey:@"position"];
circleLayer.actions = actions;
...
[circleLayer setPosition:CGPointMake(100,100)];
设置动作为[NSNull null]可以禁用这个属性的隐式动画。字典中不可以保存nil,所以必须使用NSNull类。
有一些特殊的动作用于图层树中添加图层(KCAOnOrderIn)或移除图层(KCAOnOrderOut)时,举个例子,可以创建一组变大同时淡入的动画:
CABasicAnimation *fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithDouble:0.4];
fadeAnim.toValue = [NSNumber numberWithDouble:1.0];
CABasicAnimation *growAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
growAnim.fromValue = [NSNumber numberWithDouble = 0.8];
growAnim.toValue = [NSNumber numberWithDoule:1.0];
CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
groupAnim.animations = [NSArray arrayWithObjects:fadeAnim,growAnim,nil];
[actions setObject:groupAnim forKey:KCAOnOrderIn];
在图层替换时,动作对处理过渡(KCATransition)也非常重要。一般都是与CATransition(一个特殊类型的CAAnimation)一起使用。可以针对contents属性使用CATransition动作来创建特效,比如内容改变时的幻灯片放映效果。默认是启动淡入淡出。
为自定义属性添加动画
Core Animation 隐式地为很多图层属性添加动画,但CALayer子类的自定义属性呢,比如CircleLayer中的radius属性?默认情况下,radius是没有动画的,而contents有(通过CATransition)。因此,更改半径会导致圆形渐渐消失并出现新的图形。这可能不是你想要的结果,你可能希望radius的动画效果像position一样,通过以下几步就可以完成
@implementation CircleLayer
@dynamic radius;
-(id)init
{
self = [super init];
if(self)
{
[self setNeedsDisplay];
}
}
-(id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
[self setRadius:[layer radius]];
return self;
}
-(void)drawInContext:(CGContextRef)ctx
{
CGContextSetFillColorWithColor(ctx,[[UIColor redColor]CGColor]);
CGFloat radius =self.radius;
CGRect rect;
rect.size = CGSizeMake(radius,radius);
rect.origin.x = (self.bounds.size.width - radius)/2;
rect.origin.y = (self.bounds.size.height - radius)/2;
CGContextAddEllipseInRect(ctx,rect);
CGContextFillPath(ctx);
}
+(BOOL)needsDisplayForKey:(NSString *)key
{
if([key isEqualToString:@"radius"])
{
return YES;
}
return [super needsDisplayForKey:key];
}
-(id<CAAction>)actionForKey:(NSString *)key
{
if([self presentationLayer] != nil){
if([key isEqualToString:@"radius"])
{
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath@"radius"];
anim.fromValue = [[self presentationLayer] valueForKey:@"radius"];
return animo;
}
}
return [super actionForKey:key];
}
@end
先来回顾以下基础知识。在init方法里调用setNeedsDisplay,这样图层的drawInContext会在第一次添加图层到图层树时被调用。覆盖needsDisplayForKey:方法,这样无论如何修改半径都可以自动重绘。
Core Animation为了生成动画效果会创建图层的多个副本。它使用initWithLayer:来实现复制,因此你需要实现这个方法来复制自定义的属性。
现在要修改动作了。我们实现了actionForKey:方法,一次返回一个在当前图(presentationLayer)中有半径起始值(fromValue)的动画。这意味如果动画中途变化,动画效果会更加平滑。
Core Animation 与线程
Core Animation可以很好的适应线程。通常,可以在任意线程中修改CALayer属性,这点与UIView属性不同。可以在任何线程中调用drawInContext:方法。(不过特定的CGContext只能一次在一个线程上修改。)对CALayer属性的更改会使用CATransaction按事务分配到多个线程中进行处理。如果有一个运行循环的话,这个过程就会自动发生;如果没有运行循环,则需要定期调用[CATransaction flush]。如果可能,应该在运行循环的线程中实现Core Animation动作来改善性能。