找工作过程中,iOS动画貌似问的有点多。恰巧过去并没有好好学过iOS中的动画,因此想在这里记录一下,也方便以后查看。这篇先来记录与动画有关系的CALayer。
关于CALayer的文章,网上已经有很多了。因此也是拾人牙慧了。
iOS中的视图,我们一般用UIView来绘制。而UIView之所以能够显示到屏幕上供我们看见,就是因为UIView里面包含了一层CALayer,它才是真正进行绘制的图层。当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示。
因此我们操纵calayer,就能改变UIView的显示效果。
那么UIView和CALayer有什么区别呢?UIView负责绘制的也是CALayer,它主要是接受一些事件的响应,CALayer就负责纯画图。
CALayer是被定义在QuartzCore框架中的,看网上的老教程都是说要先导入QuartzCore框架,不过现在新建的工程里已经导入了,所以能直接在工程里用了。只需引入相应的头文件。
#import <QuartzCore/QuartzCore.h>
每一个UIView就包含了一个layer,这个可以称为根layer,我们也可以自己新建layer,然后和加子view一样作为子layer加到根layer上。操作方法和加view一样一样的,只是方法名不同罢了。
[self.view.layer addSublayer:mylayer];
新建图层的方法:
CALayer *myLayer = [CALayer layer];
myLayer.bounds = CGRectMake(0, 0, 100, 100);
myLayer.position = CGPointMake(100, 100);
myLayer.backgroundColor = [UIColor redColor].CGColor;
myLayer.cornerRadius = 10;[self.view.layer addSublayer:myLayer];
其中bounds,position,backgroundColor,cornerRadius那些是CALayer的属性。
bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画;
backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画;
position:用于设置CALayer的位置。修改这个属性会产生平移动画;
anchorPoint:所说的锚点,决定着CALayer身上的哪个点会在position属性所指的位置。取值范围都是0~1,默认值为(0.5, 0.5);
cornerRadius:设置圆角;
以上是些常用的一些属性。
另外,如果是图层中需要显示图片,不能直接用UIImage:
myLayer.contents = (id)[UIImage imageNamed:@"demo.png"].CGImage;
//如加了图片,又要设置圆角,需要将masksToBounds设置成YES,才有效果
myLayer.cornerRadius = 10;
myLayer.masksToBounds = YES;
这里设置的图片是CGImageRef类型的数据,通过UIImage的CGImage属性能够转换。同样层中也不能用UIColor,需将UIColor转换为CGColorRef类型,用UIColor的CGColor属性。
为什么会这样呢?原因在于CALayer是定义在QuartzCore框架中的;CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的;UIColor、UIImage是定义在UIKit框架中的。而QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用,但是UIKit只能在iOS中使用。因此,为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef。
还有个就是选择UIView和CALayer的问题。因为UIView就比CALayer多了个事件处理功能,因此需要交互的话,就用UIView;只需要绘图的话,就用CALayer了,这样效率要高些了。
再来说说自定义CALayer的事。自定义层就是在层上绘图,有两种方法。
第一种可以通过创建一个CALayer的子类,然后覆盖drawInContext:方法,使用Quartz2D API进行绘图。仿照网上教程的一个例子:
@implementation MyLayer
#pragma mark 绘制一个实心三角形
- (void)drawInContext:(CGContextRef)ctx {
// 设置为蓝色
CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);
// 设置起点
CGContextMoveToPoint(ctx, 50, 0);
// 从(50, 0)连线到(0, 100)
CGContextAddLineToPoint(ctx, 0, 100);
// 从(0, 100)连线到(100, 100)
CGContextAddLineToPoint(ctx, 100, 100);
// 合并路径,连接起点和终点
CGContextClosePath(ctx);
// 绘制路径
CGContextFillPath(ctx);
}
@end
在需要用到的地方添加:
MyLayer *layer = [MyLayer layer];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(100, 100);
// 开始绘制图层
[layer setNeedsDisplay];
[self.view.layer addSublayer:layer];
绘制图层需要调用setNeedsDisplay方法,它会自动触发drawInContext方法的调用,进行绘图。
第二种方法是设置CALayer的delegate,然后让delegate实现drawLayer:inContext:方法,当CALayer需要绘图时,会调用delegate的drawLayer:inContext:方法进行绘图。不过不能将某个UIView设置为CALayer的delegate,因为UIView对象已经是它内部根层的delegate,再次设置为其他层的delegate会出问题。
CALayer *layer = [CALayer layer];
// 设置delegate
layer.delegate = self;
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(100, 100);
// 开始绘制图层
[layer setNeedsDisplay];
[self.view.layer addSublayer:layer];
还是需要setNeedsDisplay来通知delegate进行绘制。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 1);
CGContextSetLineWidth(ctx, 10);
// 添加一个跟层一样大的矩形到路径中
CGContextAddRect(ctx, layer.bounds);
// 绘制路径
CGContextStrokePath(ctx);
这个delegate是写在Controller中的。
另外还有个CAReplicatorLayer比较常用,它可以让其子类具有相同的属性。
附上一篇写的好的文章:http://www.jianshu.com/p/b660eb8b8bc1
最后,为什么要调用setNeedsDisplay而不直接用drawInContext?以及有次笔试题遇到的问setNeedsLayout和layoutIfNeeded各自作用是什么?有什么区别?下次再记录。