1.图层的树状结构
Core Animation的前身叫做Layer Kit,所以,你应该意识到Core Animation并不只是用来做动画的。做动画只是Core Animation特性的冰山一角。
Core Animation是一个复合引擎,他的职责就是尽可能的组合屏幕上不同的可视类容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。
这个树形成了UIKit以及在iOS应用程序中你所能在屏幕上看见的一切的基础。
视图:
一个视图就是屏幕上显示的一个矩形块(比如图片,文字或者视频),它能够拦截类似鼠标点击或者触摸手势等用户输入。视图的层级关系中可以互相嵌套,一个视图可以管理它的所有子视图。
在iOS中,所有的视图都从一个叫做UIView的基类派生而来,UIView可以处理触摸事件,可以支持基于Core Graphics绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。
图层:
CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也包含一些内容(图片、文字或者背景色),能管理所有子图层的位置。图层有一些方法和属性来做动画和变换。和UIView最大的不同是CALayer不处理用户交互,因为CALayer并不清楚具体的相应链(iOS通过视图层级关系用来传送触摸事件的机制)。但是图层提供了一些方法来判断是否一个触点在图层的范围之内。
小结:UIView可以说就是CALayer的代理,视图的职责就是创建并管理它对应的图层,以确保子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样在对应的层级关系树当中有相同的操作。
CALayer的能力:
对于一些简单的需求来说,我们没必要处理CALayer。因为苹果已经通过UIView的高级API间接地使动画变得很简单。但是UIView是对CALayer的封装,这使得UIView的灵活性远远低于CALayer。如下是UIView没有暴露出来的CALayer的功能:
1.阴影、圆角、带颜色的边框
2.3D变换
3.非矩阵范围
4.透明遮罩
5.多级非线性动画
说明:使用图层需要导入QuartzCore框架。我们一般的情况下添加UIView的时候附带添加了其对应的CALayer。我们也可以只在UIView上添加CALayer。
某些特殊情况下更需要使用CALayer:
1.开发同时可以在Mac OS上运行的跨平台应用
2.使用多种CALayer的子类,并且不想创建UIView去包装它们
3.做一些对性能特别挑剔的工作,比如对UIView一些可以忽略不计的操作都会引起显著的不同。
---------------------------
2.寄宿图(CALayer包含的图)
属性:
contents:
该属性被定义为id,但是只能给它赋CGImageRef类型的值,因为跨平台的问题(在Mac OS系统上,这个属性对CGImage和NSImage类型的值都有用)。UIImage有个CGImage属性类型是CGImageRef。但是CGImageRef并不是一个Cocoa对象,而是一个Core Foundation类型,所以必须使用桥接来赋值。
eg: layer.contents = (_bridge id)image.CGImage;
------------
contentGravity:
其实这个属性对应的就是UIView里面的contentMode属性。用法差不多,contentMode对应着一个枚举,contentGravity对应着NSString。
eg: self.layerView.layer.contentsGravity = kCAGravityResizeAspect;对应着aspectFit
说明:kCAGravityResizeAspect并不会考虑分辨率的问题,它就是拉伸图片以适应图层而已。
------------
contentScale:
这个属性并不是一直有用,比如将contentGravity设置为了kCAGravityResizeAspect,那么设置contentScale属性是没用的。但是如果你设置其为kCAGravityCenter(不会拉伸图片),那就有效果了。
contentScale属性属于支持高分辨率屏幕机制的一部分。如果设置为1那么代表每个点一个像素来绘制图片,如果设置为2那么就代表每个点2*2个像素来绘制图片。
相比较于UIImage,CGImage没有拉伸的概念。当你设置kCAGravityResizeAspect的时候其实就是强行拉伸了,所以设置contentScale没有用。所以使用CGImage来设置图层的内容,并且不拉伸的时候,一般带上这句话:
eg:
self.layerView.layer.contentsScale = image.scale;
或者
self.layerView.layer.contentsScale =
[UIScreen mainScreen].scale
;--------------
maskToBounds:
对比到UIView就是clipsToBounds属性。决定超出边界的内容是否显示。
---------------
contentRect:
CALayer的contentRect属性容许我们显示图层框内的寄宿图的局部,这涉及到图片是如何显示和拉伸的,所以要比contentGravity灵活很多。和bounds、frame不同,contentRect采用了单位坐标(值在0~1),是一个相对值(像素和点都是绝对值)。所以contentRect是相对于寄宿图的尺寸的。
eg: layer.contentRect = CGRectMake(0, 0, 0,5, 0.5);
iOS使用的坐标系统:
1.点--在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果。
2.像素 —— 物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。
3. 单位 —— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。关于contentRect属性的一个很有意思的用法--(图片拼接)
规则:像平常一样载入我们的大图,然后把它赋值给四个独立的图层的contents,然后设置每个图层的contentsRect来去掉我们不想显示的部分
- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer {
<pre name="code" class="objc">//set image
layer.contents = (__bridge id)image.CGImage;
<pre name="code" class="objc">//scale contents to fit
layer.contentsGravity = kCAGravityResizeAspect;
//set contentsRect
layer.contentsRect = rect;
}
- (void)viewDidLoad
{
[super viewDidLoad]; //load sprite sheet
UIImage *image = [UIImage imageNamed:@"Sprites.png"];//加载拼接图片
//set igloo sprite
[self addSpriteImage:image withContentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.iglooView.layer];//gei
//set cone sprite
[self addSpriteImage:image withContentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.coneView.layer];
//set anchor sprite
[self addSpriteImage:image withContentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.anchorView.layer];
//set spaceship sprite
[self addSpriteImage:image withContentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.shipView.layer];
}
拼合不仅给app提供了一个整洁的载入方式,还有效地提高了载入性能(单张大图比多张小图载入地更快),但是如果有手动安排的话,他们还是有一些不方便的,如果你需要在一个已经创建好的品和图上做一些尺寸上的修改或者其他变动,无疑是比较麻烦的。
Mac上有一些商业软件可以为你自动拼合图片,这些工具自动生成一个包含拼合后的坐标的XML或者plist文件,拼合图片的使用大大简化。这个文件可以和图片一同载入,并给每个拼合的图层设置contentsRect,这样开发者就不用手动写代码来摆放位置了。
这些文件通常在OpenGL游戏中使用,不过呢,你要是有兴趣在一些常见的app中使用拼合技术,那么一个叫做LayerSprites的开源库(https://github.com/nicklockwood/LayerSprites),它能够读取Cocos2D格式中的拼合图并在普通的Core Animation层中显示出来。
------------------------contentsCenter:
contentsCenter其实是一个Rect,它定义了一个固定的边框和一个在图层上可拉伸的区域。改变contentsCenter的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,才看得到效果。现在一个图层的大小是100*100,放一张100*100的图片上去,然后设置contentRect,显示图片的局部,但是图层的大小没变,此时图片就有了拉伸,此时再去设置contentsCenter就会有效果,前提是图片一定是拉伸了。
CGImageRef image = [UIImage imageNamed:@"3"].CGImage;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(20, 20, CGImageGetWidth(image), CGImageGetHeight(image));;
btn.layer.contents = (__bridge id)image;
btn.layer.contentsRect = CGRectMake(0.25, 0.25, 0.5, 0.5);
btn.layer.contentsCenter = CGRectMake(0.33, 0.33, 0.33, 0.33);
btn.layer.contentsGravity = kCAGravityResize;
self.btn1 = btn;
[self.view addSubview:self.btn1];
原图:
下面是局部显示的原图,上面是利用contentsCenter处理后的图片
说明:在SB里面有个Stretching就是用来设置contetsCenter的。
步骤:在SB上面拖一个Btn,选属性下面就看到了