Core Animation的学习 - 1

Core Animation ,学习底层绘画与动画

2016-05-18 | 阅读

这一系列文章,都是在学习总结https://github.com/AttackOnDobby/iOS-Core-Animation-Advanced-Techniques这里的内容.

Core Animation

Core Animation是一个复合引擎,将屏幕中的内容分解成独立的图层,存储在一个叫做图层树的体系之中.

iOS中所有视图都从UIView的基类中派生出来,UIView可以处理触碰时间,可以支持基于Core Graphics绘图,可以做仿射变换(如旋转缩放),可以做简单的滑动或渐变的动画.

CALayer

CALayer在概念上与UIView类似,也是一些被层级关系树管理的矩形块,也可以包含一些图片文字或背景色,管理子图层的位置,也可以矩形动画和变换,与UIView最大的区别是,CALayer不处理用户的交互,而UIView继承于UIResponder负责处理交互.

UIViewCALayer是平级的,平级的原因,是为了职责分离,UIView负责交互,而CALayer只负责界面的真正实现.然后再将绘图 布局 动画的逻辑分开并应用到独立的Core Animation框架中.事实上除了UIViewCALayer,还有其他两个层次.

简单属性:

  • maskToBounds : 与UIView中的clipsToBounds一样,CALayer中有这个属性,来设置是否绘制超出边界的部分
  • geometryFlipped : 决定一个图层的坐标是否相对于父图层垂直翻转.设置为YES表示会垂直翻转,也就是坐标顶部在左下角,而不是默认的左上角

contents属性

这个属性的类型是id,可以赋值任何值,但只当赋值为CGImage才有效,其他情况赋值没效果.UIImage有一个CGImage属性,返回值类型是CGImageRef,是一个Core Foundation类型,所以又要转换成id类型:

layer.contents = (__bridge id)image.CGImage;

contentMode

CALayer有类似UIView的contentMode属性contentsGravity,可选值如下:

  • kCAGravityCenter
  • kCAGravityTop
  • kCAGravityBottom
  • kCAGravityLeft
  • kCAGravityRight
  • kCAGravityTopLeft
  • kCAGravityTopRight
  • kCAGravityBottomLeft
  • kCAGravityBottomRight
  • kCAGravityResize
  • kCAGravityResizeAspect
  • kCAGravityResizeAspectFill

值的含义类似于UIViewContentModel的属性,来决定内容在图层的边界中如何对齐.

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

contentsScale属性

contentsScale属性定义了图片的像素尺寸和视图大小的比例,默认是1.0.这个属性是用来支持高分辨率屏幕的,来决定显示图片的拉伸度和图片创建的空间大小.UIView中有一个类似的属性contentScaleFactor.这个值一般来表示当前屏幕一个点上的像素数量,即1x,2x和3x.contentsScale只决定真正的图片大小,而在calayer上的显示效果,还要受其他属性影响.

CGImageUIImage不同,没有拉伸的概念,UIImage有一个scale的属性,但是直接使用CGImage时,要设置CALayerconentsScale:

  UIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layer
  self.layerView.layer.contents = (__bridge id)image.CGImage; //center the image
  self.layerView.layer.contentsGravity = kCAGravityCenter;
  //set the contentsScale to match image
  self.layerView.layer.contentsScale = image.scale;

contentsRect

这个属性允许我们在图层的边框里显示图片的一部分,contentsRect不是按点计算的,使用单位坐标,即0-1的相对值.iOS使用了一下三种常用的坐标体系:

  • 点,即逻辑像素,1x,2x,3x
  • 像素,真实的物理像素,但物理像素不用于屏幕布局.UIImage用点来度量大小,而底层的图片,如CGImage,则会使用到像素.
  • 单位, 单位坐标指定在0-1之间,指定相对的大小布局.一般在OpenGL和Core Animation中使用.

默认的contentsRect为{0,0,1,1}.

事实上给contentsRect设置负数原点或是大于1的尺寸是可以的,这种情况下最外面的像素会被拉伸以填充剩余的区域.

这个属性还可以用到的地方是image sprites.首先,载入一张大图要比载入多张小图要好很多,速度更快,渲染更快,内存使用更少.然后对于多张小图,我们可以将其拼在一张大图中,然后使用这个属性来在大图中截取小图部分来使用,以提高一丢丢的效率.

contentsCenter

contentsCenter是一个CGRect,定义了一个固定的边框和一个在图层上可以拉伸的区域.默认为{0,0,1,1},即如果大小改变了,则图片会均匀的进行拉伸.这个属性的主要目的是保证拉伸是拉伸一部分,而保存其他部分的完整性,如设置一个{0.25,0.25,0.25,0.25},然后图片就会如下图所示进行拉伸:

这种拉伸效果可以保证图片的圆角和边框的完整性.这个属性可以应用到所有的图片上,包括Core Graphics运行时绘制的图形。

但是需要注意,自由当图片拉伸时,这个属性才会生效,即如果原图比实际大小要大,则不会压缩中间部分,而是进行普通的压缩。 且实测,即使拉伸,其也无法达到很好的效果。

Custome Drawing

给contents赋值CGImage并不是唯一设置CALayer显示的方法,也可以直接通过Core Graphics来直接绘图,通过继承UIView,并实现-drawRect:方法.

-drawRect:没有默认实现,如果UIView检测到了-drawRect:方法被调用,会为视图分配一个寄宿图,而如果不需要执行绘图,就不要创建这个方法,创建了会造成资源和内存的浪费.

虽然-drawRect:是UIView中的方法,事实上是底层的CALayer安排了重绘工作和保存了图片.

CALayer有一个delegate,实现CALayerDelegate协议,但是CALayerDelegate不是一个protocol,而是

@interface NSObject (CALayerDelegate)
// 如果需要重绘,则调用这个方法. 当设置 contents,也会调用进行绘图 或者调用CALayer的display方法
- (void)displayLayer:(CALayer *)layer;
// 如果不实现displayLayer:方法,则会调用这个方法
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
layout manager处理前,进行布局操作,如果这个方法被调用,layoutmanager会被忽略
- (void)layoutSublayersOfLayer:(CALayer *)layer;

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

@end

当图层在屏幕上显示时,CALayer不会自动重绘自己的内容,重绘的决定权交给了开发者,需要显示的调用-display方法.

使用CALayerDelegate绘制时,并不支持绘制超出边界的部分.

UIView会将自己的图层的delegate设置为自己,要重绘UIView的根CALayer,只能通过drawRect:方法.所以只有当创建一个单独的图层的时候,才会使用到CALayerDelegate协议.