iOS动画基础之Layer

layer

Features

contents属性

CALayer有一个属性叫做contents,这个属性的类型被定义为id,意味着它可以是任何类型的对象。在这种情况下,你可以给contents属性赋任何值,你的app仍然能够编译通过。但是,在实践中,如果你给contents赋的不是CGImage,那么你得到的图层将是空白的。

所以基本的使用情况:

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

不过貌似本地测试的时候只是强转了id也能生效

contentsGravity

同UIImageView一样,在大小不合适的情况下,会出现照片的适应性问题。解决方法就是把contentMode属性设置成更合适的值,像这样:

view.contentMode = UIViewContentModeScaleAspectFit;

这个方法基本和我们遇到的情况的解决方法已经接近了(你可以试一下 :) ),不过UIView大多数视觉相关的属性比如contentMode,对这些属性的操作其实是对对应图层的操作。

CALayer与contentMode对应的属性叫做contentsGravity,但是它是一个NSString类型,而不是像对应的UIKit部分,那里面的值是枚举。

和cotentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎么对齐,我们将使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit, 同时它还能在图层中等比例拉伸以适应图层的边界。

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

maskToBounds

默认情况下,UIView仍然会绘制超过边界的内容或是子视图,在CALayer下也是这样的。 UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds,把它设置为YES,图片就在边界里啦~

还有一些其他属性,诸如 contentsScalecontentsRectcontentsCenter 、等觉得是并不常用,暂不做介绍了。

CAShapeLayer

CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:

  • 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
  • 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  • 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉。
  • 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。

创建一个CGPath

CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如lineWith(线宽,用点表示单位),lineCap(线条结尾的样子),和lineJoin(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。

下面的代码用一个CAShapeLayer渲染一个简单的火柴人。CAShapeLayer属性是CGPathRef类型,但是我们用UIBezierPath帮助类创建了图层路径,这样我们就不用考虑人工释放CGPath了。

UIView *containerView = [[UIView alloc] initWithFrame:self.view.bounds];
containerView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:containerView];
    
// create path
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 shapr layer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 5;
layer.lineJoin = kCALineJoinRound;
layer.lineCap = kCALineCapRound;
layer.path = path.CGPath;
    
// add layer to view
[containerView.layer addSublayer:layer];
CAShapeLayer CGPath

圆角

CAShapeLayer为创建圆角视图提供了一个方法,就是CALayer的cornerRadius属性。虽然使用CAShapeLayer类需要更多的工作,但是它有一个优势就是可以单独指定每个角。

我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形:

// define path parameters
CGRect rect = CGRectMake(0, 0, 100, 100);
UIRectCorner corners = UIRectCornerTopRight |
                       UIRectCornerBottomRight | UIRectCornerBottomLeft;
CGSize radii = CGSizeMake(30, 30);
// create path
UIBezierPath *corPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                              byRoundingCorners:corners
                                                    cornerRadii:radii];
    
CAShapeLayer *cornerLayer = [CAShapeLayer layer];
cornerLayer.frame = CGRectMake(100, 260, 100, 100);
cornerLayer.path = corPath.CGPath;
cornerLayer.fillColor = [UIColor blueColor].CGColor;
[containerView.layer addSublayer:cornerLayer];
CAShapeLayer Corner

我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把CAShapeLayer作为视图的宿主图层,而不是添加一个子视图。

CAEmitterLayer

在iOS 5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayer。CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。

CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。我们不会对这些属性逐一进行详细的描述,你们可以在CAEmitterCell类的头文件中找到。

类似于facebook的点赞效果:(这里就不加放大缩小的效果了)

CAEmitterLayer

代码如下:

CAEmitterCell *explosionCell = [CAEmitterCell emitterCell];
// The name of the cell,用于构建key paths。这也是后面手动控制动画开始和结束的关键。
explosionCell.name           = @"explosion";
explosionCell.alphaRange     = 0.10;
explosionCell.alphaSpeed     = -1.0;
// 下面两个属性如果只用了lifetime那么粒子的存活时间就是固定的,比如lifetime=10,那么粒子10s秒后就消失了。
// 如果使用了lifetimeRange,比如lifetimeRange=5,那么粒子的存活时间就是在5s~15s这个范围内消失。
explosionCell.lifetime       = 0.7; // 粒子存活的时间,以秒为单位
explosionCell.lifetimeRange  = 0.3; // 可以为这个粒子存活的时间再指定一个范围
explosionCell.birthRate      = 0; // 每秒生成多少个粒子
// 粒子平均初始速度。正数表示竖直向上,负数竖直向下
explosionCell.velocity       = 40.00;
// 可以再指定一个范围
explosionCell.velocityRange  = 10.00;
explosionCell.scale          = 0.03;
explosionCell.scaleRange     = 0.02;
explosionCell.contents       = (id)[UIImage imageNamed:@"Sparkle"].CGImage;
    
self.explosionLayer               = [CAEmitterLayer layer];
self.explosionLayer.name          = @"emitterLayer";
// 发射源的形状
self.explosionLayer.emitterShape  = kCAEmitterLayerCircle;
// 发射源的发射模式
self.explosionLayer.emitterMode   = kCAEmitterLayerOutline;
//发射源大小。注意除了宽和高之外,还有纵向深度emitterDepth
self.explosionLayer.emitterSize   = CGSizeMake(10, 0);
self.explosionLayer.emitterCells  = @[explosionCell];
self.explosionLayer.renderMode    = kCAEmitterLayerOldestFirst;
self.explosionLayer.masksToBounds = NO;
self.explosionLayer.position      = CGPointMake(10, 10);	// 发射源位置
self.explosionLayer.zPosition     = -1;	// 发射源位置
[self.button.layer addSublayer:self.explosionLayer];
    
[self.button addTarget:self action:@selector(startC) forControlEvents:UIControlEventTouchUpInside];
- (void)startC {
    //进入下一个动作
//    [self performSelector:@selector(explode) withObject:nil afterDelay:0.2];
    
//    //explosionLayer开始时间
    self.explosionLayer.beginTime = CACurrentMediaTime();
    //explosionLayer每秒喷射的2500个
    //CAEmitterLayer 根据自己的 emitterCells 属性找到名叫 explosion 的 cell,
    //并设置它的 birthRate 为 500。从而间接地控制了动画的开始。
    [self.explosionLayer setValue:@2500 forKeyPath:@"emitterCells.explosion.birthRate"];
    //停止喷射
    [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
}
/**
 *  大量喷射
 */
- (void)explode {
    //explosionLayer开始时间
    self.explosionLayer.beginTime = CACurrentMediaTime();
    //explosionLayer每秒喷射的2500个
    [self.explosionLayer setValue:@2500 forKeyPath:@"emitterCells.explosion.birthRate"];
    //停止喷射
    [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
}
/**
 *  停止喷射
 */
- (void)stop {
    [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosion.birthRate"];
}
  • emitterShape,发射源的形状,平常用的多的比如 emitterShape 的 kCAEmitterLayerLine 和 kCAEmitterLayerPoint。这两个从视觉上还是比较好区分的,这决定了你的粒子是从一个点「喷」出来的,还是从一条线上每个点「喷」下来,前者像焰火,后者像瀑布。显然,下雪的效果更像后者。
  • emitterMode 的 kCAEmitterLayerOutline 表示向外围扩散,如果你的发射源形状是 circle,那么 kCAEmitterLayerOutline 就会以一个圆的方式向外扩散开。又比如你想表达一股蒸汽向上喷的效果,就可以设置 emitterShape 为 kCAEmitterLayerLine , emitterMode 为 kCAEmitterLayerOutline。

CAEmitterLayer的属性它自己控制着整个例子系统的位置和形状。一些属性比如birthRate,lifetime和celocity,这些属性在CAEmitterCell中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个例子系统。其他值得提到的属性有以下这些:

  • preservesDepth,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层
  • renderMode,控制着在视觉上粒子图片是如何混合的。

下雪的效果

CAEmitterLayer

代码如下:

CAEmitterLayer *snowEmitter = [CAEmitterLayer layer];
    //发射点的位置
snowEmitter.emitterPosition = CGPointMake(CGRectGetWidth(self.view.frame) / 2, -30);
snowEmitter.emitterSize = CGSizeMake(CGRectGetWidth(self.view.frame) * 2, 0.0f);
snowEmitter.emitterShape = kCAEmitterLayerLine; // 发射源的形状
snowEmitter.emitterMode = kCAEmitterLayerOutline;
    
snowEmitter.shadowColor = [UIColor whiteColor].CGColor;
snowEmitter.shadowOffset = CGSizeMake(0.0f, 1.0f);
snowEmitter.shadowRadius = 0.0f;
snowEmitter.shadowOpacity = 1.0f;
    
CAEmitterCell *snowCell = [CAEmitterCell emitterCell];
    
snowCell.birthRate = 1.0f; //每秒出现多少个粒子
snowCell.lifetime = 120.0f; // 粒子的存活时间
snowCell.velocity = -10; //速度
snowCell.velocityRange = 10; // 平均速度
snowCell.yAcceleration = 2;//粒子在y方向上的加速度
snowCell.emissionRange = 0.5f * M_PI; //发射的弧度
snowCell.spinRange = 0.75f * M_PI; // 粒子的平均旋转速度
snowCell.contents = (id)[UIImage imageNamed:@"snow"].CGImage;
snowCell.color = [UIColor colorWithRed:0.6 green:0.658 blue:0.743 alpha:1.0].CGColor;
    
snowEmitter.emitterCells = @[snowCell];
    
[self.view.layer insertSublayer:snowEmitter atIndex:0];  

有时候返回的时候,layer还未移除,所以会看到部分影像,可以在返回的时候手动移除:

for (CALayer *subLayer in self.view.layer.sublayers) {
    if ([subLayer isKindOfClass:[CAEmitterLayer class]]) {
        CAEmitterLayer *la = (CAEmitterLayer *)subLayer;
        [la removeFromSuperlayer];
    }
}

CAGradientLayer

CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。

基础渐变

从一个简单的红变蓝的对角线渐变开始。

这些渐变色彩放在一个数组中,并赋给colors属性。这个数组成员接受CGColorRef类型的值(并不是从NSObject派生而来),所以我们要用通过bridge转换以确保编译正常。

CAGradientLayer也有startPoint和endPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。代码如下:

/* 
 * 简单的两种颜色的对角线渐变
 *
 */
    
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:self.contentView];
    
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:gradientLayer];
    
// set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, 
						 (__bridge id)[UIColor blueColor].CGColor];
    
// 单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);

对应的效果:

CAGradientLayer 基础渐变

多重渐变

如果你愿意,colors属性可以包含很多颜色,所以创建一个彩虹一样的多重渐变也是很简单的。默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用locations属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。

locations数组并不是强制要求的,但是如果你给它赋值了就一定要确保locations的数组大小和colors数组大小一定要相同,否则你将会得到一个空白的渐变。

重新构造一个渐变的形式:

/*
 * 多重渐变
 *
 */
    
self.contentView2 = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 100, 100)];
[self.view addSubview:self.contentView2];
    
CAGradientLayer *gradientLayer2 = [CAGradientLayer layer];
gradientLayer2.frame = self.contentView2.bounds;
[self.contentView2.layer addSublayer:gradientLayer2];
    
// set gradient colors
gradientLayer2.colors = @[(__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor yellowColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor];
    
// set locations
gradientLayer2.locations = @[@0.0, @0.25, @0.5];
    
//set gradient start and end points
gradientLayer2.startPoint = CGPointMake(0, 0);
gradientLayer2.endPoint = CGPointMake(1, 1);
locations 构造偏移至左上角的三色渐变

类似于iPhone上面的解锁提示文案, > 滑动来解锁,就使用这种方式来实现。

FB有个开源库,就是来做这种事的: facebook/Shimmer      ٩(˃̶͈̀௰˂̶͈́)و

CAReplicatorLayer

CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。

先看一下基本的动画效果,包括心形和音律跳动的动画。

locations 构造偏移至左上角的三色渐变

重复图层之音律跳动

我们先创建一个CAReplicatorLayer,并在上面添加子图层,给该子图层增加一个动画效果。之后设置复制的个数、间隔以及动画的延迟时候等,做出动画效果。

为了有个更好的参考,添加了个参考的图层。


#define SYS_DEVICE_WIDTH   ([[UIScreen mainScreen] bounds].size.width)   // 屏幕宽度
#define SYS_DEVICE_HEIGHT  ([[UIScreen mainScreen] bounds].size.height)  // 屏幕长度

CAReplicatorLayer *_musicLayer = [CAReplicatorLayer layer];
_musicLayer.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0-50, 410, 100, 100);
_musicLayer.backgroundColor = [UIColor greenColor].CGColor;
_musicLayer.masksToBounds = YES;
[self.view.layer addSublayer:_musicLayer];
    
// 创建一个子layer,并以此来作为复制的基础
CALayer *tLayer = [CALayer layer];
tLayer.backgroundColor = [UIColor redColor].CGColor;
[_musicLayer addSublayer:tLayer];
    
// 给个参考的图层
#if 0
CALayer *ttLayer = [CALayer layer];
ttLayer.backgroundColor = [UIColor blueColor].CGColor;
[_musicLayer addSublayer:ttLayer];
ttLayer.frame = CGRectMake(20, 70, 10, 40);
#endif
    
// 给该子layer加动画
CABasicAnimation *musicAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
musicAnimation.duration = 0.35;
musicAnimation.autoreverses = YES;
musicAnimation.repeatCount = MAXFLOAT;
    
// 通过改变位置关系来展现不同的动画效果
#if 0
tLayer.frame = CGRectMake(10, 70, 10, 30);
musicAnimation.fromValue = @(70);
musicAnimation.toValue = @(45);
#else
tLayer.frame = CGRectMake(10, 100, 10, 30);
musicAnimation.fromValue = @(100);
musicAnimation.toValue = @(85);
#endif
    
[tLayer addAnimation:musicAnimation forKey:@"musicAnimation"];
    
// 复制layer,会连layer的动画一同复制
_musicLayer.instanceCount = 3;  // 复制的个数
_musicLayer.instanceTransform = CATransform3DMakeTranslation(30, 0, 0);  //每个layer的间距。
_musicLayer.instanceDelay = 0.2;    // 动画的延迟时间

重复图层之心形动画

心形的动画,一方面增加了路径的绘制,另一方面也包含了颜色的减弱效果。

// love路径
UIBezierPath *tPath = [UIBezierPath bezierPath];
[tPath moveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 200)];
[tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 400) controlPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0 + 200, 20)];
[tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 200) controlPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0 - 200, 20)];
[tPath closePath];
    
// 具体的layer
UIView *tView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
tView.center = CGPointMake(SYS_DEVICE_WIDTH/2.0, 200);
tView.layer.cornerRadius = 5;
tView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    
// 动作效果
CAKeyframeAnimation *loveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
loveAnimation.path = tPath.CGPath;
loveAnimation.duration = 8;
loveAnimation.repeatCount = MAXFLOAT;
[tView.layer addAnimation:loveAnimation forKey:@"loveAnimation"];
    
CAReplicatorLayer *_loveLayer = [CAReplicatorLayer layer];
_loveLayer.instanceCount = 40;                // 40个layer
_loveLayer.instanceDelay = 0.2;               // 每隔0.2出现一个layer
_loveLayer.instanceColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor;
_loveLayer.instanceGreenOffset = -0.03;       // 颜色值递减。
_loveLayer.instanceRedOffset = -0.02;         // 颜色值递减。
_loveLayer.instanceBlueOffset = -0.01;        // 颜色值递减。
[_loveLayer addSublayer:tView.layer];
[self.view.layer addSublayer:_loveLayer];

在 Demo 中,新增了类实现了一下CAReplicatorLayer的效果,这里就放个图欣赏下,具体代码查看 Demo 吧。

CAReplicatorLayer

反射之倒影效果

使用CAReplicatorLayer并应用一个负比例变换于一个复制图层,你就可以创建指定视图(或整个视图层次)内容的镜像图片,这样就创建了一个实时的『反射』效果。

UIImage *image = [UIImage imageNamed:@"fireballoon"];
    
// 定义ReplicatorLayer
CAReplicatorLayer *layer = [CAReplicatorLayer layer];
[layer setBounds:CGRectMake(0, 0, image.size.width, image.size.height * 1.5)];
layer.masksToBounds =  YES;
layer.anchorPoint = CGPointMake(0.5, 0.0);  // 设置锚点为layer的中间上面部分
layer.position = CGPointMake(self.view.frame.size.width/2, 80.0); // 设置坐标
layer.instanceCount = 2;
[self.view.layer addSublayer:layer];
    
// 设置图片的翻转并移动到合适的位置
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DScale(transform, 1.0, -1.0, 1.0);
transform = CATransform3DTranslate(transform, 0, -[image size].height * 2, 1.0);
    
layer.instanceTransform = transform;
    
// 设置子layer
_imageLayer = [CALayer layer];
[_imageLayer setContentsScale:[[UIScreen mainScreen] scale]];
[_imageLayer setContents:(__bridge id)image.CGImage];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
[_imageLayer setAnchorPoint:CGPointMake(0, 0)];
    
[layer addSublayer:_imageLayer];
CAReplicatorLayer 倒影

当然,以view的形式来规定layer,也是一个很简单的方式。详见 Demo

其他效果

1、在后面追增一个形式,根据笔迹来运动,直接看动画吧,就不上代码了:

CAReplicatorLayer 倒影

2、奉上苹果官方效果:看到效果的我乐起来~~

Apple ReplicatorDemo

CAScrollLayer

对于一个未转换的图层,它的bounds和它的frame是一样的,frame属性是由bounds属性自动计算而出的,所以更改任意一个值都会更新其他值。

但是如果你只想显示一个大图层里面的一小部分呢。比如说,你可能有一个很大的图片,你希望用户能够随意滑动,或者是一个数据或文本的长列表。在一个典型的iOS应用中,你可能会用到UITableView或是UIScrollView,但是对于独立的图层来说,什么会等价于刚刚提到的UITableView和UIScrollView呢?

之前,探索了图层的contentsRect属性的用法,它的确是能够解决在图层中小地方显示大图片的解决方法。但是如果你的图层包含子图层那它就不是一个非常好的解决方案,因为,这样做的话每次你想『滑动』可视区域的时候,你就需要手工重新计算并更新所有的子图层位置。

这个时候就需要CAScrollLayer了。CAScrollLayer有一个-scrollToPoint:方法,它自动适应bounds的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。前面提到过,Core Animation并不处理用户输入,所以CAScrollLayer并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。

不同于UIScrollView,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。图层内容极有可能滑出视图的边界并无限滑下去。CAScrollLayer并没有等同于UIScrollView中contentSize的属性,所以当CAScrollLayer滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。 那你一定会奇怪用CAScrollLayer的意义到底何在,因为你可以简单地用一个普通的CALayer然后手动适应边界原点啊。真相其实并不复杂,UIScrollView并没有用CAScrollLayer,事实上,就是简单的通过直接操作图层边界来实现滑动。

CAScrollLayer有一个潜在的有用特性。如果你查看CAScrollLayer的头文件,你就会注意到有一个扩展分类实现了一些方法和属性:

- (void)scrollPoint:(CGPoint)p;
- (void)scrollRectToVisible:(CGRect)r;
@property(readonly) CGRect visibleRect;

看到这些方法和属性名,你也许会以为这些方法给每个CALayer实例增加了滑动功能。但是事实上他们只是放置在CAScrollLayer中的图层的实用方法。scrollPoint:方法从图层树中查找并找到第一个可用的CAScrollLayer,然后滑动它使得指定点成为可视的。scrollRectToVisible:方法实现了同样的事情只不过是作用在一个矩形上的。visibleRect属性决定图层(如果存在的话)的哪部分是当前的可视区域。如果你自己实现这些方法就会相对容易明白一点,但是CAScrollLayer帮你省了这些麻烦,所以当涉及到实现图层滑动的时候就可以用上了。

具体代码查看 Demo

CATransformLayer

当我们在构造复杂的3D事物的时候,如果能够组织独立元素就太方便了。比如说,你想创造一个孩子的手臂:你就需要确定哪一部分是孩子的手腕,哪一部分是孩子的前臂,哪一部分是孩子的肘,哪一部分是孩子的上臂,哪一部分是孩子的肩膀等等。

当然是允许独立地移动每个区域的啦。以肘为指点会移动前臂和手,而不是肩膀。Core Animation图层很容易就可以让你在2D环境下做出这样的层级体系下的变换,但是3D情况下就不太可能,因为所有的图层都把他的孩子都平面化到一个场景中。

CATransformLayer解决了这个问题,CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构,比如我的手臂示例。

CATiledLayer

CATiledLayer,是载入大图时对性能提升有帮助的layer,将大图分解成小片然后将他们单独按需载入。不详解。

GitHub地址

1、Demo地址:

LayerAnimation

2、为了更好的理解各种layer的,推荐一下开源软件,里面集合和多种layer的效果:

LayerPlayer

查考

iOS核心动画高级技巧

iOS - CAReplicatorLayer的运用

基于CAReplicatorLayer的炫酷动画

IOS使用CAReplicatorLayer重建动态的倒影

CAReplicatorLayer的使用