原文出处:http://blog.csdn.net/zhz459880251/article/details/50470447
参考: 本文是学习https://zsisme.gitbooks.io/ios-/content/index.html后进行的整理, 更多详细的内容可以去看看
在iOS当中,所有的视图都从一个叫做UIVIew的基类派生而来,UIView可以处理触摸事件,可以支持基于Core Graphics绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。
CALayer也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。和UIView最大的不同是CALayer不处理用户的交互。
每一个UIview都有一个CALayer实例的图层属性, 这些图层才是真正用来在屏幕上显示和做动画,UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。
UIView的高级API间接地使得动画变得很简单。
但UIView还有没有暴露出来的CALayer的功能:
阴影,圆角,带颜色的边框
3D变换
非矩形范围
透明遮罩
多级非线性动画
创建layer的创建和创建View其实差不多
CALayer *blueLayer = [CALayer layer];
//1. 设置 frame
blueLayer.frame = CGRectMake(50.0f,50.0f,100.0f,100.0f);
//2. 设置背景色
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
//添加到父layer上
[self.view.layer addSublayer:blueLayer];
1. contents: 寄宿图, 类型被定义为id, 但是其实是CGImage类型的;
layer.contents= (__bridge id)image.CGImage;
注意: 如果使用ARC,__bridge没必要使用
UIImage*image = [UIImageimageNamed:@"1.png"];
self.layerView.layer.contents= (id)image.CGImage;
这样一个不需要UIImageView就能显示图片的额View就建立起来了.
2. contentGravity: 对contents中显示的图片伸缩处理
在UIImageView中如果图片拉伸了, 解决方法就是把contentMode属性设置成更合适的值,像这样:
view.contentMode=UIViewContentModeScaleAspectFit;
CALayer与contentMode对应的属性叫做contentsGravity,但是它是一个NSString类型。contentsGravity可选的常量值有以下一些:
kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill
self.view.layer.contentsGravity= kCAGravityResizeAspect;
2. contentsScale: 寄宿图的像素尺寸和视图大小的比例, 默认为1.0;
如果设置了contentsGravity = kCAGravityResizeAspect属性,说明寄宿图已经被拉伸以适应图层的边界, 所以再设置contentsScale对屏幕上的寄宿图没有影响.contentsGravity = kCAGravityCenter有很明显的变化
如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕。
self.view.layer.contentsScale= [UIScreen mainScreen].scale;
3. maskToBounds: 超出边界是否剪切, bool类型
UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds
YES: 超出部分 不显示
NO: 超出部分也显示
CALayer的contentsRect属性允许我们在图层边框里显示寄宿图的一个子域。
和bounds,frame不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。iOS使用了以下的坐标系统:
点—— 在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果。
像素—— 物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。
单位—— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。
默认的contentsRect是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果我们指定一个小一点的矩形,图片就会被裁剪
默认的contentsRect是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果我们指定一个小一点的矩形,图片就会被裁剪
事实上给contentsRect设置一个负数的原点或是大于{1, 1}的尺寸也是可以的。这种情况下,最外面的像素会被拉伸以填充剩下的区域。
contentsRect另一个重要用途:
拼合技术–图片拼合后可以打包整合到一张大图上一次性载入。相比多次载入不同的图片,这样做能够带来很多方面的好处:内存使用,载入时间,渲染性能等等
规则很简单:像平常一样载入我们的大图,然后把它赋值给四个独立的图层的contents,然后设置每个图层的contentsRect来去掉我们不想显示的部分。
@interfaceViewController()
@property(nonatomic,weak)IBOutletUIView*coneView;
@property(nonatomic,weak)IBOutletUIView*shipView;
@property(nonatomic,weak)IBOutletUIView*iglooView;
@property(nonatomic,weak)IBOutletUIView*anchorView;
@end
@implementationViewController
- (void)viewDidLoad
{
[superviewDidLoad];//load sprite sheet
UIImage*image = [UIImageimageNamed:@"Sprites.png"];
//set igloo sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0,0,0.5,0.5) toLayer:self.iglooView.layer];
//set cone sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0.5,0,0.5,0.5) toLayer:self.coneView.layer];
//set anchor sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0,0.5,0.5,0.5) toLayer:self.anchorView.layer];
//set spaceship sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0.5,0.5,0.5,0.5) toLayer:self.shipView.layer];
}
- (void)addSpriteImage:(UIImage*)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer//set image{
layer.contents= (__bridgeid)image.CGImage;
//scale contents to fit
layer.contentsGravity= kCAGravityResizeAspect;
//set contentsRect
layer.contentsRect= rect;
}@end
Mac上有一些商业软件可以为你自动拼合图片,这些工具自动生成一个包含拼合后的坐标的XML或者plist文件,拼合图片的使用大大简化。这个文件可以和图片一同载入,并给每个拼合的图层设置contentsRect,这样开发者就不用手动写代码来摆放位置了。
5. contentsCenter: 与 UIImage里的-resizableImageWithCapInsets: 方法效果非常类似
用于layer边界的拉伸
layer.contentsCenter=CGRectMake(0.25, 0.25, 0.5, 0.5)
7.conrnerRadius: 圆角弧度
默认情况下,这个曲率值只影响背景颜色而不影响背景图片或是子图层
这条线(也被称作stroke)沿着图层的bounds绘制,同时也包含图层的角
9.borderColor: 边框颜色, CGColorRef
CGColorRef属性即便是强引用也只能通过assign关键字来声明
borderColor定义了边框的颜色,默认为黑色
边框是绘制在图层边界里面的,而且在所有子内容之前,也在子图层之前
11. shadowOpacity: 必须在0.0(不可见)和1.0(完全不透明)之间的浮点数
使用CALayer的另外三个属性:shadowColor,shadowOffset和shadowRadius。
和borderColor和backgroundColor一样,它的类型也是CGColorRef。阴影默认是黑色
13. shadowOffset: 属性控制着阴影的方向和距离
它是一个CGSize的值,宽度控制这阴影横向的位移,高度控制着纵向的位移。shadowOffset的默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移。
当它的值是0的当值越来越大的时候,边界线看上去就会越来越模糊和自然。苹果自家的应用设计更偏向于自然的阴影,所以一个非零值再合适不过了。
shadowRadius属性控制着阴影的模糊度,当它的值是0的时候,阴影就和视图一样有一个非常确定的边界线
我们已经知道图层阴影并不总是方的,而是从图层内容的形状继承而来
但是实时计算阴影也是一个非常消耗资源的,尤其是图层有多个子图层,每个图层还有一个有透明效果的寄宿图的时候。
如果你事先知道你的阴影形状会是什么样子的,你可以通过指定一个shadowPath来提高性能
如果是一个矩形或者是圆,用CGPath会相当简单明了。但是如果是更加复杂一点的图形,UIBezierPath类会更合适,它是一个由UIKit提供的在CGPath基础上的Objective-C包装类。
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。
当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:
渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉(如我们在第二章所见)。
不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
属性比如
lineWith(线宽,用点表示单位),
lineCap(线条结尾的样子),
lineJoin(线条之间的结合点的样子);
在下一篇时会详细说明一些用法, 及一些事亻列,
Core Animation提供了一个CALayer的子类CATextLayer,它以图层的形式包含了UILabel几乎所有的绘制特性,并且额外提供了一些新的特性。
CATextLayer也要比UILabel渲染得快得多
如果我们想以Retina的质量来显示文字,我们就得手动地设置CATextLayer的contentsScale属性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。
有startPoint和endPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。
下一篇详细介绍, 及一些事例
CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。
instanceCount属性指定了图层需要重复多少次。
instanceTransform指定了一个CATransform3D3D变换(这种情况下,下一图层的位移和旋转将会移动到圆圈的下一个点)。
instanceBlueOffset和instanceGreenOffset: 逐步减少蓝色和绿色通道
CAReplicatorLayer*layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount =2;
//move reflection instance below original and flip vertically
CATransform3Dtransform =CATransform3DIdentity;
CGFloatverticalOffset =self.bounds.size.height +2;
transform = CATransform3DTranslate(transform,0, verticalOffset,0);
transform = CATransform3DScale(transform,1, -1,0);
layer.instanceTransform = transform;
//reduce alpha of reflection layer
layer.instanceAlphaOffset = -0.6;
6.CAScrollLayer
7.CATiledLayer
CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入
8.CAEmitterLayer
在iOS5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayer。CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
9.AVPlayerLayer
它不是Core Animation框架的一部分(AV前缀看上去像),AVPlayerLayer是有别的框架(AVFoundation)提供的,它和Core Animation紧密地结合在一起,提供了一个CALayer子类来显示自定义的内容类型。
AVPlayerLayer是用来在iOS上播放视频的。他是高级接口例如MPMoivePlayer的底层实现,提供了显示视频的底层控制。AVPlayerLayer的使用相当简单:你可以用+playerLayerWithPlayer:方法创建一个已经绑定了视频播放器的图层,或者你可以先创建一个图层,然后用player属性绑定一个AVPlayer实例。
NSURL*URL = [[NSBundlemainBundle]URLForResource:@"Ship"withExtension:@"mp4"];
//create player and player layer
AVPlayer *player = [AVPlayer playerWithURL:URL];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
//set player layer frame and attach it to our view
playerLayer.frame =self.containerView.bounds;
[self.containerView.layer addSublayer:playerLayer];
//play the video
[player play];
我们用代码创建了一个AVPlayerLayer,但是我们仍然把它添加到了一个容器视图中,而不是直接在controller中的主视图上添加。这样其实是为了可以使用自动布局限制使得图层在最中间;否则,一旦设备被旋转了我们就要手动重新放置位置,因为Core Animation并不支持自动大小和自动布局
AVPlayerLayer是CALayer的子类,它继承了父类的所有特性。我们并不会受限于要在一个矩形中播放视频;圆角,有色边框,蒙板,阴影等效果
CALayer自定义绘制
给contents赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现-drawRect:方法来自定义绘制。
-drawRect:方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到-drawRect: 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值
但是创建-drawRect:会造成CPU资源和内存的浪费;
当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用。-drawRect:方法里面的代码利用Core Graphics去绘制一个寄宿图
UIView有三个比较重要的布局属性:frame,bounds和center,CALayer对应地叫做frame,bounds和position。为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值。
frame: 代表了图层的外部坐标(也就是在父图层上占据的空间)
bounds是内部坐标({0, 0}通常是图层的左上角)
center和position都代表了相对于父图层anchorPoint(中心点)所在的位置
视图的frame,bounds和center属性仅仅是存取方法,当操纵视图的frame,实际上是在改变位于视图下方CALayer的frame,不能够独立于图层之外改变视图的frame。
注意: 1. 对于视图或者图层来说,frame并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值
2. 当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽高可能和bounds的宽高不再一致了
视图的center属性和图层的position属性都指定了anchorPoint相对于父图层的位置。图层的anchorPoint通过position来控制它的frame的位置,你可以认为anchorPoint是用来移动图层的把柄。
默认来说,anchorPoint位于图层的中点,所以图层的将会以这个点为中心放置。anchorPoint属性并没有被UIView接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的anchorPoint可以被移动,比如你可以把它置于图层frame的左上角,于是图层的内容将会向右下角的position方向移动,而不是居中了。
contentsRect和contentsCenter属性类似,anchorPoint用单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。anchorPoint可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。
一个图层的position依赖于它父图层的bounds
CALayer给不同坐标系之间的图层转换提供了一些工具类方法:
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形.
和UIView严格的二维坐标系不同,CALayer存在于一个三维空间当中。除了position和anchorPoint属性之外,还有zPosition和anchorPointZ,二者都是在Z轴上描述图层位置的浮点类型。
zPosition最实用的功能就是改变图层的显示顺序了
给zPosition提高一个像素就可以改变视图显示顺序,
self.greenView.layer.zPosition=1.0f;
Hit Testing
CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:
- containsPoint:和- hitTest:。
- containsPoint:接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。这需要把触摸坐标转换成每个图层坐标系下的坐标
-hitTest:方法同样接受一个CGPoint类型参数,它返回不是BOOL类型,而是图层本身,或者包含这个坐标点的叶子节点图层。如果这个点在最外面图层的范围之外,则返回nil。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//get touch position
CGPoint point = [[touches anyObject] locationInView:self.view];
//get touched layer
CALayer *layer = [self.layerView.layer hitTest:point];
//get layer using hitTest
if(layer ==self.blueLayer) {
[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}elseif(layer ==self.layerView.layer) {
[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
注意: 当调用图层的-hitTest:方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件类似)。之前提到的zPosition属性可以明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。
这意味着如果改变了图层的z轴顺序,你会发现将不能够检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的zPosition值较小,但是在图层树中的顺序靠前。
当使用视图的时候,可以充分利用UIView类接口暴露出来的UIViewAutoresizingMask和NSLayoutConstraintAPI
通过masksToBounds属性,我们可以沿边界裁剪图形;通过cornerRadius属性,我们还可以设定一个圆角。但是有时候你希望展现的内容不是在一个矩形或圆角矩形
CALayer有一个属性叫做mask可以解决这个问题
这个属性本身就是个CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,图层定义了父图层的部分可见区域
如果mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。
“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;
3D变换
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
CATransform3D transform = CATransform3DMakeRotation(M_PI_4,0,1,0);
self.layerView.layer.transform = transform;