书籍是人类进步的阶梯
总览思维导图
一、图层树
1.1.contents
简介:(id类型),虽然是id类型但如果给contents赋的不是CGImage,那么得到的图层将是空白的。
layer.contents = (__bridge id)image.CGImage;注意:在加载图片时为了适应视图,我们一般这么处理:
view.contentMode = UIViewContentModeScaleAspectFit;
但在CALayer与contentMode
对应的属性叫做contentsGravity
:
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
1.2 contentsScale
- 寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数:当用代码来处理寄宿图的时候,一定要记住要手动的设置图层的contentsScale属性,否则,你的图片在Retina设备上就显示得不正确啦。代码如下:
layer.contentsScale = [UIScreen mainScreen].scale;
1.3 maskToBounds
- UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds
1.4 contentsRect
- 默认的contentsRect是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果设置{0,0,0.5,0.5},那只显示左上角部分(整体的1/4部分)
1.5 contentsCenter
-
其实是一个CGRect,它定义了一个固定的边框和一个
在图层上可拉伸的区域。默认情况下,contentsCenter是{0, 0, 1, 1},这意
味着如果大小(由conttensGravity决定)改变了,那么寄宿图将会均匀地拉
伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造
一个边框。图2.9展示了contentsCenter设置为{0.25, 0.25, 0.5, 0.5}的效果。
1.6 -drawRect:方法:
- 当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用;
- 调用了-setNeedsDisplay方法时,-drawRect:方法会被调用;
二、图层几何学
2.1 frame、bounds、position
-
当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽高可能和bounds的宽高不再一致了
2.2 锚点anchorPoint
- anchorPoint是用来移动图层的把柄。anchorPoint用单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}
2.3zPosition
- 在大多数情况下其实并不常用。zPosition最实用的功能就是改变图层的显示顺序了。通过增加图层的zPosition,就可以把图层向相机方向前置,于是它就在所有其他图层的前面了(或者至少是小于它的zPosition值的图层的前面)。
2.4 -containsPoint
- 接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint point = [[touches anyObject] locationInView:self.view];
point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
//get layer using containsPoint:
if ([self.layerView.layer containsPoint:point]) {
//convert point to blueLayer’s coordinates
point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
if ([self.blueLayer containsPoint:point]) {
// your code
} else {
// your other code
}
}
}
2.5 -hitTest
- 方法接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层:
- (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) {
// your code
} else if (layer == self.layerView.layer) {
// your other code
}
}
三、专用图层CALayer
3.1 CAShapeLayer
- 使用CAShapeLayer的优点:
- 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多;
- 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存;
- 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。
- 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
1.CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。
2.CAShapeLayer属性是CGPathRef类型
- 绘制圆角:( 需求:三个圆角,一个直角)
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
3.2 CATextLayer
- CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。
- CATextLayer使用了Core text,并且渲染得非常快。可以尝试封装一个CATextLayer用于替换UILabel
UILabel的替代品
- 使用CATextLayer来封装一个UILabel的子类
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass
{
//this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
return [CATextLayer class];
}
- (CATextLayer *)textLayer
{
return (CATextLayer *)self.layer;
}
- (void)setUp
{
//set defaults from UILabel settings
self.text = self.text;
self.textColor = self.textColor;
self.font = self.font;
//we should really derive these from the UILabel settings too
//but that's complicated, so for now we'll just hard-code them
[self textLayer].alignmentMode = kCAAlignmentJustified;
[self textLayer].wrapped = YES;
[self.layer display];
}
- (id)initWithFrame:(CGRect)frame
{
//called when creating label programmatically
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
//called when creating label using Interface Builder
[self setUp];
}
- (void)setText:(NSString *)text
{
super.text = text;
//set layer text
[self textLayer].string = text;
}
- (void)setTextColor:(UIColor *)textColor
{
super.textColor = textColor;
//set layer text color
[self textLayer].foregroundColor = textColor.CGColor;
}
- (void)setFont:(UIFont *)font
{
super.font = font;
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
[self textLayer].font = fontRef;
[self textLayer].fontSize = font.pointSize;
CGFontRelease(fontRef);
}
@end
3.3 CATransformLayer
- CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构
3.4 CATiledLayer
- CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入
3.5 CAGradientLayer
- CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。
3.6 CAReplicatorLayer:(反射效果)
- CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。
3.7 CAEmitterLayer(粒子、火焰特效)
- 简介CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
- CAEmitterCell:
CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage。
CAEMitterCell的属性
- 这种粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。
- 例子某一属性的变化范围。比如emissionRange属性的值是2π,这意味着例子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形。
- 指定值在时间线上的变化。比如,在示例中,我们将alphaSpeed设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐消失的效果。
- preservesDepth:是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层。
- renderMode:控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为kCAEmitterLayerAdditive,它实现了这样一个效果:合并例子重叠部分的亮度使得看上去更亮。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);
//create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
//add particle template to emitter
emitter.emitterCells = @[cell];
}
@end
3.8 CAEAGLLayer
- 用来显示任意的OpenGL图形,一般用不到。
3.9 AVPlayerLayer
- AVPlayerLayer是CALayer的子类,它继承了父类的所有特性,主要用于视频播放。
4.0 CAScrollLayer
- UIView中的UIScrollView的底层封装。
四、视觉效果
4.1 圆角
- conrnerRadius:控制着图层角的曲率。(只影响背景颜色而不影响背景图片或是子图层),一般和masksToBounds配合着使用。
- borderWidth:定义边框粗细
- borderColor:边框的颜色
4.2 阴影
- shadowOpacity:0.0(不可见)和1.0(完全不透明)之间的浮点数;
- shadowColor:控制阴影的颜色;
- shadowOffset:控制阴影的方向和距离。默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移;
- shadowRadius:控制着阴影的模糊度,当值为0时,阴影和视图有非常确定的边界线。值越大,边界线看上去就会越来越模糊和自然;
- shadowPath:一个CGPathRef类型(一个指向CGPath的指针)。我们可以通过这个属性单独于图层形状之外指定阴影的形状;
- mask:mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。代码演示:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.layerView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
//apply mask to image layer
self.imageView.layer.mask = maskLayer;
}
@end
效果:
4.3拉伸过滤:
view.layer.magnificationFilter = kCAFilterNearest;
4.4 组透明
- 透明度会叠加,即当一个控件有子控件时,设置父控件的透明度(UIView对应alpha、CALayer对应opacity),子控件的透明度也会被影响。设置CALayer的一个叫做shouldRasterize属性来实现组透明的效果,如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了:
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
五、变换
5.1 仿射变换CGAffineTransform(2D变换)
5.1.1 原理:
1. UIView的transform属性是一个CGAffineTransform类型,用于在二维空间做旋转,缩放和平移;
2. CALayer对应于UIView的transform属性叫做affineTransform;
3. CALayer同样也有一个transform属性,但它的类型是CATransform3D。
5.1.2 主要方法:
CGAffineTransformMakeRotation(CGFloat angle); //旋转
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy); //缩放
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty); //平移
5.1.3 混合变换:CGAffineTransformIdentity
// 需求:先缩小50%,再旋转30度,最后向右移动200个像素
- (void)viewDidLoad
{
[super viewDidLoad];
//create a new transform
CGAffineTransform transform = CGAffineTransformIdentity;
//scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5);
//rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
//translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
}
3D变换CATransform3D
5.2.1 主要方法:
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z); //旋转
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz); //缩放
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz); //平移
5.2.2 透视投影:m34
m34的默认值是0,我们可以通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,通常500-1000就已经很好了。
5.2.3 灭点
当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点(在现实中,这个点通常是视图的中心)
- sublayerTransform:它也是CATransform3D类型,它会影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//apply perspective transform to container
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//rotate layerView1 by 45 degrees along the Y axis
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform1;
//rotate layerView2 by 45 degrees along the Y axis
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView2.layer.transform = transform2;
}
六、隐式动画
事务(CATransaction)
- 事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值
主要用法:
[CATransaction begin]; // 入栈
[CATransaction commit]; //出栈
+setAnimationDuration: //设置当前事务的动画时间
+animationDuration // 获取值(默认0.25秒)
[CATransaction setDisableActions:YES]; //对所有属性关闭隐式动画
隐式动画如何实现:
- 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
- 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
- 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
- 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。
所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。
七、显式动画
7.1 关键帧动画(CAKeyframeAnimation)
和CABasicAnimation类似,CAKeyframeAnimation同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。
- 关键帧起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就是关键帧),每帧之间剩下的绘制(可以通过关键帧推算出)将由熟练的艺术家来完成。CAKeyframeAnimation也是同样的道理:你提供了显著的帧,然后Core Animation在每帧之间进行插入。
代码:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
...
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
// 平移动画
animation.keyPath = @"position";
//持续时间
animation.duration = 4.0;
// 动画路径
animation.path = bezierPath.CGPath;
// *图层将会根据曲线的切线自动旋转*
animation.rotationMode = kCAAnimationRotateAuto;
[shipLayer addAnimation:animation forKey:nil];
}
八、动画组CAAnimationGroup
- CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性,用来组合别的动画。实例代码:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add a colored layer
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
colorLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.containerView.layer addSublayer:colorLayer];
//create the position animation
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
animation1.keyPath = @"position";
animation1.path = bezierPath.CGPath;
animation1.rotationMode = kCAAnimationRotateAuto;
//create the color animation
CABasicAnimation *animation2 = [CABasicAnimation animation];
animation2.keyPath = @"backgroundColor";
animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
//create group animation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1, animation2];
groupAnimation.duration = 4.0;
//add the animation to the color layer
[colorLayer addAnimation:groupAnimation forKey:nil];
}
九、CATransition(过渡动画)
-
type(动画类型)
- kCATransitionFade (淡入淡出)
- kCATransitionMoveIn(从顶部滑动进入)
- kCATransitionPush
- kCATransitionReveal
-
subtype(动画方向)
- kCATransitionFromRight
- kCATransitionFromLeft
- kCATransitionFromTop
- kCATransitionFromBottom
在动画过程中取消动画
- (void)removeAnimationForKey:(NSString *)key; // 移除某个动画
- (void)removeAllAnimations; // 移除所有动画
十、CAMediaTiming协议
10.1 概念
- CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
10.2 属性
- duration:CFTimeInterval的类型,对将要进行的动画的一次迭代指定了时间;
- repeatCount:动画重复的迭代次数;
- repeatDuration:动画重复一个指定的时间,而不是指定次数;
- autoreverses:(BOOL类型)在每次间隔交替循环过程中自动回放。
10.3 注意
- duration和repeatCount默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅代表了“默认”,也就是0.25秒和1次;
- 把repeatDuration设置为INFINITY,于是动画无限循环播放,设置repeatCount为INFINITY也有同样的效果。
- repeatCount和repeatDuration可能会相互冲突,所以你只要对其中一个指定非零值.
10.4 相对时间
- beginTime:指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)
- speed:是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了.
- timeOffset:增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始.
基于定时器的动画
NSTimer并不准确的原因:
iOS上的每个线程都管理了一个NSRunloop,通过一个循环来完成一些任务列表。当你设置一个NSTimer,他会被插入到当前任务列表中,然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久才完成就会导致延迟很长一段时间.
性能优化(待完善)
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;