CoreAnimation学习笔记(一)
这是关于系统学习CoreAnimation的学习笔记,记录常用和重要的知识点,gitbook地址:https://www.gitbook.com/book/zsisme/ios-/details
一、寄宿图
(一)、contents属性设置寄宿图
1,替换根layer类型
+ (Class)layerClass 可替换根layer的类型
2, 给layer设置图片,ARC需要使用__bridge
layer.contents = (__bridge id)image.CGImage; //ARC需要使用__bridge
3,layer设置图片的填充模式,contentGravity属性,值为NSString类型的常量.
//和imageView的contentMode的UIViewContentModeScaleAspectFit效果相同
self.layerView.layer.contentGravity = kCAGravityResizeAspect;
4,layer的contentsScale属性
这个属性在layer使用了contentGravity属性时可能会使contentsScale属性不起作用。contentsScale这个属性是使layer一个点站几个像素,如果没有设置contentGravity或者设置的不是填充拉伸相关的模式,那么设置layer.contentGravity = [UIScreen mainScreen].scale;能够保证在retina屏上一个点显示两个像素.但使如果设置了layer. contentGravity = kCAGravityResizeAspect;那么图片的大小会按比例显示在layer内部。如果是layer. contentGravity = kCAGravityCenter;那么会按照屏幕的分辨率去显示,即如果像素是100X100,那么在[UIScreen mainScreen].scale = 2时显示的是50X50个点大小图片。
5,layer的contentsRect属性
用于设置layer显示图片的位置, contentsRect的起点和宽高是0~1的浮点数。{0,0,0.5,0.5}代表原图的左上角(1/4)部分。可用来拆分一个整图成几个小图,拆分代码如下:
- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer //set image
{
layer.contents = (__bridge id)image.CGImage;
//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];
//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];
}
6,layer的contentsCenter属性
这是一个很容易误解的属性,它用于设置图片拉伸的范围,是一个CGRect类型的属性,也是0~1取值。类似UIImage的resizableImageWithCapInsets:方法类似。另外在interface Builder中有与contentsCenter相同功能的设置:
类似UIImage的(二)、重绘方式添加contents
为layer设置代理,注意在dealloc中销毁代理。在代理方法中实现需要绘制的图案,但是不能直接设置layer.contents,也不能直接用[test draw...](不知道什么原因,这样没有效果),通过函数绘制是有效的:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
CALayer *blueLayer = [CALayer layer];
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
blueLayer.frame = CGRectMake(50, 50, 140, 140);
blueLayer.delegate = self; //设置代理
blueLayer.contentsScale = [UIScreen mainScreen].scale;
[self.layerView.layer addSublayer:blueLayer];
_blueLayer = blueLayer;
[blueLayer display]; //layer强制重绘
}
/**
* 4,代理方法,执行display后的代理
*/
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 5);
CGContextAddEllipseInRect(ctx, layer.bounds);
CGContextStrokePath(ctx); //需要执行绘制
}
- (void)dealloc {
_blueLayer.delegate = nil;
}
二、图层几何学
(一)、布局
这里有几个很重要的概念:position(位置)和archorPoint(锚点),position是子layer相对于父layer的位置点,archorPoint是子layer上的哪个点相对于父控件的archorPoint,archorPoint是0~1的值,默认是{0.5,0.5}。
frame是一个虚拟概念,它由position, bounds,transform共同计算得到,同样,frame的改变也会对这些属性的值。
layer也可以像View那样转化layer的position或frame:
convertPoint: fromLayer:
convertRect: fromLayer:
convertPoint: toLayer:
convertRect: toLayer:
layer是在三维空间中,除了平面属性还有zPosition,anchorPointZ属性.
(二)、hit Testing
调用layer的hitTesting实例方法可以返回点击了的layer:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CALayer *blueLayer = [CALayer layer];
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
blueLayer.bounds = CGRectMake(0, 0, 140, 140);
blueLayer.position = CGPointMake(self.layerVIew.frame.size.width * 0.5, self.layerVIew.frame.size.height * 0.5);
[self.layerVIew.layer addSublayer:blueLayer];
_blueLayer = blueLayer;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self.view];
CALayer *layer = [self.view.layer hitTest:point];
if (layer == self.view.layer) {
NSLog(@"点击到view上");
}
else if (layer == self.layerVIew.layer) {
NSLog(@"点击到layerView上");
}
else if (layer == self.blueLayer) {
NSLog(@"bluelayer 上");
}
}
三,视觉效果
(一),圆角,边框,阴影,mask
默认情况下cornerRadius只影响颜色不能影响图片和子层。用masksTobounds可以剪切掉图片和子层的超出部分。
阴影需要设置不透明度shadowOpacity属性大于默认值0才能够显示,可以设置颜色,偏移量和阴影半径。阴影还可以通过layer.shadowPath属性来设置:
//enable layer shadows
self.layerView1.layer.shadowOpacity = 0.5f;
self.layerView2.layer.shadowOpacity = 0.5f;
//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
self.layerView1.layer.shadowPath = squarePath;
CGPathRelease(squarePath);
//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
self.layerView2.layer.shadowPath = circlePath;
CGPathRelease(circlePath);
mask可以是shapeLayer也可以是设置了layer.contents的普通layer。UIView也有相应的maskView属性。图片的imageView作为maskView设置后有内容的部分才会显示出来。
(二),拉伸过滤
layer在设置contents(图片)缩小和放大是有过滤器属性minificationFilter(缩小拉伸过滤器)和magnificationFilter(放大拉伸过滤器).这两个属性有三个可选值:
kCAFilterLinear
kCAFilterNearest
kCAFilterTrilinear
它们的默认值都是kCAFilterLinear,通常表现是很好的,但是kCAFilterNearest在没有斜线时表现最好,它是通过武断的通过最近的点来填充,如果有斜线就会有。kCAFilterTrilinear是三维上取样,同kCAFilterLinear相近。总结就是如果斜线极少时用kCAFilterNearest可以提高性能和显示效果,但是如果很多斜线缩小时kCAFilterNearest的表现效果会很糟糕。
(三)组透明
在自定义控件时,父控件设置了透明度,子控件透明度也会相应地改变,叠加在父控件上就会出现透明度不一的情况(经测试未出现这种情况,可能是系统已经优化),做如下处理可以解决:
superView.layer.shouldRasterize = YES;
superView.layer.rasterizationScale = [UIScreen mainScreen].scale;
四,变换
(一)放射变换
UIView的transform属性是2为的变换是CGAffineTransform类型的变量,CALayer的transform是CATransform3D类型的变量。CALayer有一个affineTransform的getter方法,获取CALayer的transform是CATransform3D类型的变量中的二维变换信息。变换后的frame是变换后通过的最左最右最上最下点的水平矩形的frame,如下图:
UIView和layer变换后的frame简单的二维变换有:
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty);
CGAffineTransformMakeRotation(CGFloat angle);
CGAffineTransformMakeScale(CGFloat sx,CGFloatsy);
需要实现多个变换同时起效可以使用复合变换,复合变换有:
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
初始时,什么都不变换的初始值:
CGAffineTransformIdentity
通过两个已经存在的矩阵变换得到新的矩阵变换:
CGAffineTransformConcat(t1, t2)
(二)3D变换
3D变换的简单方法有:
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
1,设置透视时要设置m34,注意结果应为浮点数,整数相除为0就没有透视效果:
CATransform3D transform = CATransform3DIdentity;
//apply perspective
transform.m34 = - 1.0 / 500.0;
//rotate by 45 degrees along the Y axis
transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
//apply to layer
self.layerView.layer.transform = transform;
2,灭点,透视时所有的视图图层的边线无限延伸聚焦的点,在同一屏幕应该为同一点。透视时,多个试图的3D变换属性的m34应该相同.
3,sublayerTransform设置子layer共同的属性,如设置相同的m34属性
//通过sublayerTransform设置共同的m34属性
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;
self.containView.layer.sublayerTransform = perspective;
//rotate layerView1 by 45 degrees along the Y axis
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.leftView.layer.transform = transform1;
//rotate layerView2 by 45 degrees along the Y axis
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.rightView.layer.transform = transform2;
4,背面doubleSided,默认是YES
如果设置为NO,那么反转过后,就会消失掉。
5,扁平化图层
平面旋转时,外部和内部做相同大小的反向旋转,内部的不会旋转
CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
self.outerView.layer.transform = outer;
//rotate the inner layer -45 degrees
CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
self.innerView.layer.transform = inner;
结果是:
内部没有旋转立体旋转时,内部的会做扁平化处理,如果不做处理,与现实不符:
CATransform3D outer = CATransform3DIdentity;
outer.m34 = -1.0 / 500.0;
outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);
self.outerView.layer.transform = outer;
//rotate the inner layer -45 degrees
CATransform3D inner = CATransform3DIdentity;
inner.m34 = -1.0 / 500.0;
inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);
self.innerView.layer.transform = inner;
结果是:
内部做了扁平化处理
(三)固体对象
通过6个面的变换,得到一个空心的方形体:
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform
{
//get the face view and add it to the container
UIView *face = self.faces[index];
[self.containerView addSubview:face];
//center the face view within the container
CGSize containerSize = self.containerView.bounds.size;
face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
// apply the transform
face.layer.transform = transform;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up the container sublayer transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//add cube face 1
CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
[self addFace:0 withTransform:transform];
//add cube face 2
transform = CATransform3DMakeTranslation(100, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
[self addFace:1 withTransform:transform];
//add cube face 3
transform = CATransform3DMakeTranslation(0, -100, 0);
transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
[self addFace:2 withTransform:transform];
//add cube face 4
transform = CATransform3DMakeTranslation(0, 100, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
[self addFace:3 withTransform:transform];
//add cube face 5
transform = CATransform3DMakeTranslation(-100, 0, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
[self addFace:4 withTransform:transform];
//add cube face 6
transform = CATransform3DMakeTranslation(0, 0, -100);
transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
[self addFace:5 withTransform:transform];
}
结果是:
立方体五,专用图层
(一),CAShapeLayer
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类,使用CAShapeLayer有以下一些优点:
- 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
- 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
- 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉(如我们在第二章所见)。
- 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
下面代码绘制火柴人:
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
[path moveToPoint:CGPointMake(150, 125)];
[path addLineToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(125, 225)];
[path moveToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(175, 225)];
[path moveToPoint:CGPointMake(100, 150)];
[path addLineToPoint:CGPointMake(200, 150)];
//create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 5;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.path = path.CGPath;
//add it to our view
[self.containerView.layer addSublayer:shapeLayer];
也可以绘制带有圆角的矩形,可以只绘制一个圆角:
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(10, 10);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
(二) CATextLayer,应用不多
(三) CATransformLayer
因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在:
@implementation ViewController
- (CALayer *)faceWithTransform:(CATransform3D)transform
{
//create cube face layer
CALayer *face = [CALayer layer];
face.frame = CGRectMake(-50, -50, 100, 100);
//apply a random color
CGFloat red = (rand() / (double)INT_MAX);
CGFloat green = (rand() / (double)INT_MAX);
CGFloat blue = (rand() / (double)INT_MAX);
face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
//apply the transform and return
face.transform = transform;
return face;
}
- (CALayer *)cubeWithTransform:(CATransform3D)transform
{
//create cube layer
CATransformLayer *cube = [CATransformLayer layer];
//add cube face 1
CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 2
ct = CATransform3DMakeTranslation(50, 0, 0);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 3
ct = CATransform3DMakeTranslation(0, -50, 0);
ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 4
ct = CATransform3DMakeTranslation(0, 50, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 5
ct = CATransform3DMakeTranslation(-50, 0, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 6
ct = CATransform3DMakeTranslation(0, 0, -50);
ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//center the cube layer within the container
CGSize containerSize = self.containerView.bounds.size;
cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
//apply the transform and return
cube.transform = transform;
return cube;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up the perspective transform
CATransform3D pt = CATransform3DIdentity;
pt.m34 = -1.0 / 500.0;
self.containerView.layer.sublayerTransform = pt;
//set up the transform for cube 1 and add it
CATransform3D c1t = CATransform3DIdentity;
c1t = CATransform3DTranslate(c1t, -100, 0, 0);
CALayer *cube1 = [self cubeWithTransform:c1t];
[self.containerView.layer addSublayer:cube1];
//set up the transform for cube 2 and add it
CATransform3D c2t = CATransform3DIdentity;
c2t = CATransform3DTranslate(c2t, 100, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
CALayer *cube2 = [self cubeWithTransform:c2t];
[self.containerView.layer addSublayer:cube2];
}
结果是:
通过CGTransformLayer创建的立方体
(四)CAGradientLayer
渐变layer:
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:gradientLayer];
//set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];
//set locations
gradientLayer.locations = @[@0.0, @0.25, @0.5];
//set gradient start and end points
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
结果是:
渐变色(五)CAReplicatorLayer
1,重复图层
@interface CAReplicatorLayerVC ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@end
@implementation CAReplicatorLayerVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:replicator];
//configure the replicator
replicator.instanceCount = 20;
//apply a transform for each instance
CATransform3D transform = CATransform3DIdentity;
// transform = CATransform3DTranslate(transform, 0, 50, 0);
transform = CATransform3DRotate(transform, M_PI / 10.0, 0, 0, 1);
// transform = CATransform3DTranslate(transform, 0, -50, 0);
replicator.instanceTransform = transform;
CABasicAnimation *animation = [CABasicAnimation animation];
animation.fromValue = @1;
animation.toValue = @0.5;
animation.keyPath = @"transform.scale";
animation.duration = 2.0;
animation.repeatCount = MAXFLOAT;
animation.fillMode = kCAFillModeBackwards;
animation.removedOnCompletion = NO;
//apply a color shift for each instance
replicator.instanceBlueOffset = -0.05;
replicator.instanceGreenOffset = -0.05;
replicator.instanceDelay = 0.05; //动画延迟执行
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50.0f, 50.0f, 30.0f, 30.0f);
layer.backgroundColor = [UIColor whiteColor].CGColor;
[layer addAnimation:animation forKey:@"test"];
[replicator addSublayer:layer];
}
2,反射, 外部的对称轴是ReflectionView的底边,对称的内容可以是layer,也可以是UIView及子类,克隆的layer超出CAReplicatorLayer部分也会显示 .
@implementation ReflectionView
+ (Class)layerClass
{
return [CAReplicatorLayer class];
}
- (void)setUp
{
//configure replicator
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount = 2;
//move reflection instance below original and flip vertically
CATransform3D transform = CATransform3DIdentity;
CGFloat verticalOffset = self.bounds.size.height;
// transform = CATransform3DTranslate(transform, 0, 100, 0);
transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
transform = CATransform3DScale(transform, 1, -1, 0);
layer.instanceTransform = transform;
//reduce alpha of reflection layer
layer.instanceAlphaOffset = -0.6;
}
- (id)initWithFrame:(CGRect)frame
{
//this is called when view is created in code
if ((self = [super initWithFrame:frame])) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
//this is called when view is created from a nib
[super awakeFromNib];
[self setUp];
}
@end
控制器中:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//注意反射的layer是以下边缘为对称轴,可以加view也可以是layer
UIImage *image = [UIImage imageNamed:@"1242x2208"];
UIImageView *imageVIew = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.reflectionView.frame.size.width, self.reflectionView.frame.size.height * 1)];
imageVIew.image = image;
[self.reflectionView addSubview:imageVIew];
//也可以添加layer
// CALayer *layer = [CALayer layer];
// layer.contents = (__bridge id)image.CGImage;
// layer.frame = self.reflectionView.bounds;
// [self.reflectionView.layer addSublayer:layer];
}
(六) CAScrollLayer可以无限滚动,不会做边界控制.
(七) CATiledLayer可以将图片分片加载,用于由小图片拼合大图,注意tileSize的大小默认为256,切的小图应与设置的一样.
@implementation CATileLayerVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
CATiledLayer *tileLayer = [CATiledLayer layer];
tileLayer.frame = CGRectMake(0, 0, 1242 * 0.5, 2208 * 0.5);
tileLayer.delegate = self;
[self.baseScrollView.layer addSublayer:tileLayer];
tileLayer.contentsScale = [UIScreen mainScreen].scale;
// tileLayer.tileSize = CGSizeMake(400, 400);//可以修改默认的tileSize大小,但大图切成小图时也要改为相应的尺寸图片
//configure the scroll view
self.baseScrollView.contentSize = tileLayer.frame.size;
_tileLayer = tileLayer;
//draw layer,调用后滑动才会调用代理方法
[tileLayer setNeedsDisplay];
}
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
CGFloat scale = [UIScreen mainScreen].scale;
NSInteger x = floor(bounds.origin.x / layer.tileSize.width * scale);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height * scale);
NSLog(@"scale时:x:%li--y:%li",(long)x,(long)y);
//load tile image
NSString *imageName = [NSString stringWithFormat: @"1242x2208_%02i_%02i", x, y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
//draw tile
//绘制图片
UIGraphicsPushContext(ctx);
{
[tileImage drawInRect:bounds];
}
UIGraphicsPopContext();
}
- (void)dealloc {
_tileLayer.delegate = nil;
}
(八) CAEmitterLayer粒子效果layer
(九) CAEAGLLayer可显示OpenGL图形,如三角形渐变色,三个顶点分别是rbg。
(十) AVPlayerLayer显示视频,可对视频做变换操作:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
NSURL *url = [[NSBundle mainBundle] URLForResource:@"out" withExtension:@"mp4"];
AVPlayer *player = [AVPlayer playerWithURL:url];
_player = player;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / 500.0;
transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
playerLayer.transform = transform;
playerLayer.backgroundColor = [UIColor yellowColor].CGColor;
playerLayer.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:playerLayer];
}
效果如下:
经过变换后的效果六,隐式动画
所谓隐式动画是没有指定动画的类型,仅仅改变一个属性,Core Animation来决定如何并且合适去做动画。
(一),事务
指定图层属性修改后不会立即执行,在提交事务后统一执行。图层的事务是通过CATransaction类来管理的,通过+begin和+commit方法分别来入栈和岀栈。[CATransaction setDisableActions:YES];可以取消隐式动画。UIView的动画内部其实也是通过layer提交事务来做的。
/**
* 修改颜色
*/
- (void)test1changeColor {
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
#warning 取消隐式动画
// [CATransaction setDisableActions:YES];
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
[CATransaction commit];
}
(二),完成块
图层也提供了UIView动画一样的功能,动画完成后的block执行块。
[CATransaction begin];
//set the animation duration to 1 second
[CATransaction setAnimationDuration:1.0];
//add the spin animation on completion
[CATransaction setCompletionBlock:^{
// [CATransaction setAnimationDuration:1.0];
//rotate the layer 90 degrees
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
//randomize the layer background color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
//commit the transaction
[CATransaction commit];
(三),图层行为
如果对UIView的layer做颜色修改操作,不会像对单独的layer操作有隐式的动画。这是因为UIView的layer实现动画需要实现以下条件。我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,实质上是如下几步:
- 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
- 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
- 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。
所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。
于是我们知道,UIView没有隐式动画是因为- actionForLayer: forKey: 方法直接返回了nil来禁用隐式动画。layer做动画时也可以用[CATransaction setDisableActions:YES];方法来禁用隐式动画。
- (void)viewDidLoad
{
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
//add a custom action
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.colorLayer.actions = @{@"backgroundColor": transition};
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
}
- (IBAction)changeColor
{
//randomize the layer background color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
这个是实现了layer的actions字典,执行的是actions字典中的动画:
通过actions实现隐式动画(四),呈现与模型
修改layer的属性时,其实值是当时就改变了,但是动画的呈现是通过layer的presentationLayer来实现的。下面的例子可以在动画过程中点击显示的界面改变颜色,但是如果[layer hitTest:]方法,则达不到这个效果.
- (void)viewDidLoad
{
[super viewDidLoad];
//create a red layer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//get the touch point
CGPoint point = [[touches anyObject] locationInView:self.view];
//check if we've tapped the moving layer
//如果改为if ([self.colorLayer hitTest:point]) 则要点击结束点才有改变颜色的效果.
if ([self.colorLayer.presentationLayer hitTest:point]) {
//randomize the layer background color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
} else {
//otherwise (slowly) move the layer to new position
[CATransaction begin];
[CATransaction setAnimationDuration:4.0];
self.colorLayer.position = point;
[CATransaction commit];
}
}
七,显式动画
(一)属性动画CAPropertyAnimation
属性动画包括两个子类:基础动画CABasicAnimation和关键帧动画CAKeyframeAnimation。
animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;可以实现动画结束后,layer保持动画结束时的状态。
CABasicAnimation的属性: animation.byValue是相对值结果,在animation.fromValue之上相加,animation.toValue是绝对值结果。
另外,这里给出了一个比较繁琐的方式--动画结束后通过代理设置layer的属性为toValue的值,但是会有一个瞬间闪动的状态:
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
}
- (IBAction)changeColor
{
//create a new random color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)color.CGColor;
animation.delegate = self;
//1,如果是view这里打标签,可以在代理中判断是哪个view执行了动画
// [animation setValue:handView forKey:@"handView"];
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
}
//通过代理进行反锁的实现,这里可以通过animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;这两句来实现。
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
//set the backgroundColor property to match animation toValue
[CATransaction begin];
//2,这里可以取出1中设置的view,对view进行操作
//UIView *handView = [anim valueForKey:@"handView"];
[CATransaction setDisableActions:YES];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
@end
(二)关键帧动画CAKeyframeAnimation
1,可以设置keyPath,values和keyTimes,通过关键值达到变化的效果
/**
* 关键帧动画改变颜色
*/
- (void)changeColorOfKeyFrameAnimation {
CAKeyframeAnimation *animaiton = [CAKeyframeAnimation animation];
animaiton.keyPath = @"backgroundColor";
animaiton.duration = 2;
animaiton.values = @[(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor orangeColor].CGColor,(__bridge id)[UIColor greenColor].CGColor,(__bridge id)[UIColor blueColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor];
animaiton.removedOnCompletion = NO;
animaiton.fillMode = kCAFillModeForwards;
[self.colorLayer addAnimation:animaiton forKey:nil];
}
2,通过设置path路径的方式达到变化的效果,这里animation.rotationMode = kCAAnimationRotateAuto,可以使箭头沿着path的方向转动. kCAAnimationRotateAutoReverse是反方向.
-
(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
_bezierPath = bezierPath;
//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 the ship
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 64, 64);
shipLayer.position = CGPointMake(0, 150);
shipLayer.contents = (__bridge id)[UIImage imageNamed: @"toTop.png"].CGImage;
shipLayer.transform = CATransform3DMakeRotation(M_PI_2, 0, 0, 1);
[self.containerView.layer addSublayer:shipLayer];
//create the keyframe animation
_shipLayer = shipLayer;
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = _bezierPath.CGPath; //沿着path的方向转动 animation.rotationMode = kCAAnimationRotateAuto; [_shipLayer addAnimation:animation forKey:nil]; }
3,虚拟属性
对layer做旋转操作时,如果小于180度可以用:
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform";
animation.duration = 2.0;
animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI, 0, 0, 1)];
但是如果角度大于180度,旋转就会有问题,因为对于CATransform3D变换360度和0度是一样的。幸运的是,有一个更好的解决方案:为了旋转图层,我们可以对transform.rotation关键路径应用动画,并且不仅可以做绝对量的toValue还可以做相对量的byValue而对CATransform3D进行操作是:
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = 2.0;
animation.byValue = @(M_PI * 2);
[shipLayer addAnimation:animation forKey:nil];
结果运行的特别好,用transform.rotation而不是transform做动画的好处如下:
- 我们可以不通过关键帧一步旋转多于180度的动画。
- 可以用相对值而不是绝对值旋转(设置byValue而不是toValue)。
- 可以不用创建CATransform3D,而是使用一个简单的数值来指定角度。
- 不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)。
transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体,也没有符合KVC相关属性,transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性。
你不可以直接设置transform.rotation或者transform.scale,他们不能被直接使用。当你对他们做动画时,Core Animation自动地根据通过CAValueFunction来计算的值来更新transform属性。
CAValueFunction用于把我们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你可以通过设置CAPropertyAnimation的valueFunction属性来改变,于是你设置的函数将会覆盖默认的函数。
(三)动画组
CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//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)];
_bezierPath = bezierPath;
//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];
_colorLayer= colorLayer;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//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.fromValue = (__bridge id)[UIColor greenColor].CGColor;
animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
//create group animation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1, animation2];
groupAnimation.duration = 4.0;
groupAnimation.removedOnCompletion = NO;
groupAnimation.fillMode = kCAFillModeForwards;
//add the animation to the color layer
[_colorLayer addAnimation:groupAnimation forKey:nil];
}
(四)过渡动画
1,过渡
CATransition过渡动画,type属性有四个类型的值:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
子类型subType属性也有四个类型的值:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
下面是例子
- (NSArray *)images {
if (!_images) {
_images = @[[UIImage imageNamed:@"1242x2208_00_00.png"],
[UIImage imageNamed:@"1242x2208_00_01.png"],
[UIImage imageNamed:@"1242x2208_00_07.png"],
[UIImage imageNamed:@"1242x2208_00_08.png"]];
}
return _images;
}
- (IBAction)switchImage
{
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromTop;
//开始时的进度
transition.startProgress = 0.0;
//结束时的进度
transition.endProgress = 1.0;
//apply transition to imageview backing layer
[self.imageView.layer addAnimation:transition forKey:nil];
//cycle to next image
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
2,对图层树的动画
CATransition并不作用于指定的图层属性,这就是说你可以在即使不能准确得知改变了什么的情况下对图层做动画,例如,在不知道UITableView哪一行被添加或者删除的情况下,直接就可以平滑地刷新它,或者在不知道UIViewController内部的视图层级的情况下对两个不同的实例做过渡动画。
这些例子和我们之前所讨论的情况完全不同,因为它们不仅涉及到图层的属性,而且是整个图层树的改变–我们在这种动画的过程中手动在层级关系中添加或者移除图层。
这里用到了一个小诡计,要确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,否则CATransition将会和图层一起被移除。一般来说,你只需要将动画添加到被影响图层的superlayer。
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstViewController alloc] init];
UIViewController *viewController2 = [[SecondViewController alloc] init];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = @[viewController1, viewController2];
self.tabBarController.delegate = self;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
//apply transition to tab bar controller's view
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
}
@end
3,UIView对图层转场动画的实现:
- (void)viewDidLoad
{
[super viewDidLoad]; //set up images
self.images = @[[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]];
}
- (IBAction)switchImage
{
[UIView transitionWithView:self.imageView duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
//cycle to next image
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
completion:NULL];
}
4,自定义动画
- (IBAction)performTransition
{
//preserve the current view snapshot
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
//insert snapshot view in front of this one
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.bounds;
[self.view addSubview:coverView];
//update the view (we'll simply randomize the layer background color)
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//perform animation (anything you like)
[UIView animateWithDuration:1.0 animations:^{
//scale, rotate and fade the view
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
transform = CGAffineTransformRotate(transform, M_PI_2);
coverView.transform = transform;
coverView.alpha = 0.0;
} completion:^(BOOL finished) {
//remove the cover view now we're finished with it
[coverView removeFromSuperview];
}];
}
结果是 :
自定义动画
(五) 在动画过程中取消动画
获取和移除动画的方法:
- (CAAnimation *)animationForKey:(NSString *)key;
- (void)removeAnimationForKey:(NSString *)key;
- (void)removeAllAnimations;
如果removedOnCompletion不设置为NO,那么动画结束后会自动移除。
如果是执行完动画flag为YES,如果是停止动画flag为NO:
- (void)viewDidLoad
{
[super viewDidLoad];
//add the ship
self.shipLayer = [CALayer layer];
self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
self.shipLayer.position = CGPointMake(150, 150);
self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"toTop.png"].CGImage;
[self.containerView.layer addSublayer:self.shipLayer];
}
- (IBAction)start
{
//animate the ship rotation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = 2.0;
animation.byValue = @(M_PI*1.5 );
animation.delegate = self;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
}
- (IBAction)stop
{
//移除动画,flag为NO,[self.shipLayer animationForKey:@"rotateAnimation"]也为nil
[self.shipLayer removeAnimationForKey:@"rotateAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
//log that the animation stopped
NSLog(@"The animation stopped (finished: %@)---%@", flag? @"YES": @"NO",[self.shipLayer animationForKey:@"rotateAnimation"]);
}
八,图层时间
(一) CAMediaTiming协议
CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
1,持续和重复
duration是一个CFTimeInterval的类型(类似于NSTimeInterval的一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。CAMediaTiming另外还有一个属性叫做repeatCount,代表动画重复的迭代次数。如果duration是2,repeatCount设为3.5(三个半迭代),那么完整的动画时长将是7秒。repeatDuration动画执行的总时间。
你甚至设置一个叫做autoreverses的属性(BOOL类型)在每次间隔交替循环过程中自动回放。这对于播放一段连续非循环的动画很有用,例如打开一扇门,然后关上它:
- (void)viewDidLoad
{
[super viewDidLoad];
//add the door
CALayer *doorLayer = [CALayer layer];
doorLayer.frame = CGRectMake(0, 0, 128, 256);
doorLayer.position = CGPointMake(150 - 64, 150);
doorLayer.anchorPoint = CGPointMake(0, 0.5);
doorLayer.contents = (__bridge id)[UIImage imageNamed: @"Door.png"].CGImage;
[self.containerView.layer addSublayer:doorLayer];
//apply perspective transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
//这里可以是doorLayer.transform = perspective;
self.containerView.layer.sublayerTransform = perspective;
//apply swinging animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation.y";
animation.toValue = @(-M_PI_2);
animation.duration = 2.0;
animation.repeatDuration = INFINITY;
animation.autoreverses = YES;
[doorLayer addAnimation:animation forKey:nil];
}
上面的self.containerView.layer.sublayerTransform = perspective;可以用doorLayer.transform = perspective;替换,因为内部的sublayer就doorLayer一个。
2,相对时间
beginTime指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)。
speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。
timeOffset和beginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。
但是我设置beginTime没有成功,并且设置speed为0暂停动画也没成功。
3,手动动画
- (void)viewDidLoad
{
[super viewDidLoad];
//add the door
self.doorLayer = [CALayer layer];
self.doorLayer.frame = CGRectMake(0, 0, 128, 256);
self.doorLayer.position = CGPointMake(150 - 64, 150);
self.doorLayer.anchorPoint = CGPointMake(0, 0.5);
self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage;
[self.containerView.layer addSublayer:self.doorLayer];
//apply perspective transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//add pan gesture recognizer to handle swipes
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
[pan addTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:pan];
//pause all layer animations
self.doorLayer.speed = 0.0;
//apply swinging animation (which won't play because layer is paused)
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation.y";
animation.toValue = @(-M_PI_2);
animation.duration = 1.0;
[self.doorLayer addAnimation:animation forKey:nil];
}
- (void)pan:(UIPanGestureRecognizer *)pan
{
//get horizontal component of pan gesture
CGFloat x = [pan translationInView:self.view].x;
//convert from points to animation duration //using a reasonable scale factor
x /= 200.0f;
//update timeOffset and clamp result
CFTimeInterval timeOffset = self.doorLayer.timeOffset;
timeOffset = MIN(0.999, MAX(0.0, timeOffset - x));
self.doorLayer.timeOffset = timeOffset;
//reset pan gesture
[pan setTranslation:CGPointZero inView:self.view];
}
因为在动画添加到图层之后不能再做修改了,我们来通过调整layer的timeOffset达到效果,但是速度需要设置为0.0 self.doorLayer.speed = 0.0;
九,缓冲
(一)动画速度
可以给动画CAAnimation设置animation.timingFunction来控制动画的速度,也可以通过给事务CATransaction设置timingFunction来设置事务中的多个动画的速度。
这里有一些方式来创建CAMediaTimingFunction,最简单的方式是调用+timingFunctionWithName:的构造方法。这里传入如下几个常量之一(CALayer对应的):
kCAMediaTimingFunctionLinear
kCAMediaTimingFunctionEaseIn
kCAMediaTimingFunctionEaseOut
kCAMediaTimingFunctionEaseInEaseOut
kCAMediaTimingFunctionDefault
1,下面是通过layer来实现的控制动画速度的例子:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a red layer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
self.colorLayer.position = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0);
self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//configure the transaction
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
//set the position
self.colorLayer.position = [[touches anyObject] locationInView:self.view];
//commit transaction
[CATransaction commit];
}
2,UIView控制动画速度的参数有:
UIViewAnimationOptionCurveEaseInOut
UIViewAnimationOptionCurveEaseIn
UIViewAnimationOptionCurveEaseOut
UIViewAnimationOptionCurveLinear
以下是通过UIView来实现的控制动画速度的例子:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a red layer
self.colorView = [[UIView alloc] init];
self.colorView.bounds = CGRectMake(0, 0, 100, 100);
self.colorView.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
self.colorView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.colorView];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//perform the animation
[UIView animateWithDuration:1.0 delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
//set the position
self.colorView.center = [[touches anyObject] locationInView:self.view];
}
completion:NULL];
}
3,缓冲和关键帧动画
CAKeyframeAnimation有一个NSArray类型的timingFunctions属性,我们可以用它来对每次动画的步骤指定不同的计时函数。但是指定函数的个数一定要等于keyframes数组的元素个数减一,因为它是描述每一帧之间动画速度的函数:
- (void)viewDidLoad
{
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
}
- (IBAction)changeColor
{
//create a keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"backgroundColor";
animation.duration = 2.0;
animation.values = @[
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor ];
//add timing function
CAMediaTimingFunction *fn = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
animation.timingFunctions = @[fn, fn, fn];
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
}
(二) 自定义缓冲函数
除了+functionWithName:之外,CAMediaTimingFunction同样有另一个构造函数,一个有四个浮点参数的+functionWithControlPoints::::,可以定义两个控制点的x,y分别是cp1x,cp1y,cp2x,cp2y。CAMediaTimingFunction有一个叫做-getControlPointAtIndex:values:的方法,可以用来检索曲线的点。注意values:用服点数组接收,不是CGPoint。index是从0-3的整数,0,3代表起点和终点,1,2代表起点和终点的控制点,可以用UIBezierPath和CAShapeLayer来把它画出来:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//create timing function
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
//get control points
float cp1[2],cp2[2];
[function getControlPointAtIndex:1 values:cp1];
[function getControlPointAtIndex:2 values:cp2];
CGPoint controlPoint1,controlPoint2;
controlPoint1 = CGPointMake(cp1[0], cp1[1]);
controlPoint2 = CGPointMake(cp2[0], cp2[1]);
//create curve
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointZero];
[path addCurveToPoint:CGPointMake(1, 1)
controlPoint1:controlPoint1 controlPoint2:controlPoint2];
//scale the path up to a reasonable size for display
[path applyTransform:CGAffineTransformMakeScale(200, 200)];
//create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 4.0f;
shapeLayer.path = path.CGPath;
[self.layerView.layer addSublayer:shapeLayer];
//flip geometry so that 0,0 is in the bottom-left
self.layerView.layer.geometryFlipped = YES;
}