iOS-CALayer (三)

上一篇 : iOS-CALayer (二)

前言:继续深入学习动画,主要从变换、专用图层出发。

一、变换

用于图层旋转、摆放、扭曲、扁平、三维。

1.1 仿射变换

UIView 的 transform 属性,是 CGAffineTransform 类型,用于在二维空间做旋转、缩放和平移。
CGAffineTransform 是一种可以和二维空间向量(比如 CGpoint)做乘法的 3*2 矩阵。

iOS-CALayer (三)_第1张图片
矩阵表示

对图层应用的变换矩阵,图层矩形内的每一个点都被相应的做变换,形成一个新的四边形的形状。CGAffineTransform 无论变换矩阵用什么值,图层中平行线变换后依然保持平衡。


iOS-CALayer (三)_第2张图片
仿射和非仿射

CGAffineTransform 创建:

  • 缩放:CGAffineTransformMakeScale(CGFloat sx>, CGFloat sy)
  • 旋转:CGAffineTransformMakeRotation(CGFloat angle)
  • 平移:CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

iOS 变换函数使用弧度而不是角度作为单位,弧度使用数学常量 PI 表示,比如1/4 PI = 45°。
可以写一个宏用于角度转换为弧度#define angleValue(angle) ((angle) * M_PI / 180.0)

原理:已知角度 angle 求弧度radian?
PI/180 = radian/angle;
radian = PI * angle / 180;

1.2 混合变换

如果需要做一个既要缩放又要旋转的效果,可以使用混合变换的函数。混合变换的顺序会影响最终的结果,比如先缩放50%,在平移,那么平移的数据也会缩小相应比例。
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
示例:先缩小50%,再旋转30°,最后平移200px。

    CGAffineTransform transform = CGAffineTransformIdentity;
    //缩放 50%
    transform = CGAffineTransformScale(transform, 0.5, 0.5);
    //旋转30°
    transform = CGAffineTransformRotate(transform, angleValue(30));
    //平移 200px
    transform = CGAffineTransformTranslate(transform, 200, 0);
    
    self.clockView.layer.affineTransform = transform;
1.3 3D 变换

CGAffineTransform 属于 Core Graphics 框架类型, Core Graphics严格来说是2D 绘图 API。CGAffineTransform仅仅对2D 变化有效。
CGTransform3D 是 Core Animation 框架属性,也是一个矩阵,是可以在三维空间做变换的 4*4 矩阵。

iOS-CALayer (三)_第3张图片
3D像素点CGTransform3D变换

CGTransform3D 创建:

  • 缩放:CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
  • 旋转:CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
  • 平移:CATransform3DMakeTranslation(CGFloat tx, CGFloat ty, CGFloat tz)
iOS-CALayer (三)_第4张图片
X、Y、Z 轴,及围绕旋转方向
1.4 透视投影

CGTransform3D 透视效果通过一个矩阵中 m34 元素控制,m34用于按比例缩放 X 和 Y 的值来计算到底离视角多远。m34 默认值为0,设置 m34 = -1.0/d应用透视效果,d 表示想象中视角相机与屏幕之间的距离,已像素为单位。通常 d 值为 500-1000。

iOS-CALayer (三)_第5张图片
m34 元素

    CATransform3D transform3D = CATransform3DIdentity;
    transform3D.m34 = - 1.0/500;
    transform3D = CATransform3DRotate(transform3D, M_PI_4, 0, 1, 0);
    self.clockView.layer.transform = transform3D;
iOS-CALayer (三)_第6张图片
效果图
1.5 灭点(锚点)

远离相机视角的物体就会变小,远离到极限距离就会缩成一个点,所有物体最后汇聚消失再同一个点。这个点通常是视图的中心。当改变图层的 transform 也改变了它的锚点,当通过 m34 调整增加3D 效果,首先应把图层放在屏幕中央,然后通过平移移动到指定位置,这样所有3D 图层都共享一个锚点。


iOS-CALayer (三)_第7张图片
灭点(锚点)
1.6 sublayerTransform 属性

多个视图/图层每个都做3D 变换,需要分别设置相同的 m34值,确保变换之前都在屏幕中央共享一个 position。CALayer 有 sublayerTransform 属性,它影响所有子图层。

    CATransform3D subTransform = CATransform3DIdentity;
    subTransform.m34 = - 1.0/ 500;
    self.view.layer.sublayerTransform = subTransform;
    
    CATransform3D tran1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    CATransform3D tran2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
    
    self.clockView1.layer.transform = tran1;
    self.clockViewv2.layer.transform = tran2;
iOS-CALayer (三)_第8张图片
效果图
1.7 背面

背面即是旋转180°,达到完全背对相机的视角,图层是双面绘制的,反面显示正面的镜像图片。
上述不是最佳方法,因为:
如果图层包含文本或其他控件

  • 会给用户造成疑惑
  • 造成资源浪费
  • 如果图层是不透明固态立方体,永远看不到图层的反面

CALayer 的属性 doubleSided 控制图层背面是否要绘制,默认为 YES,如何设置为 NO,当图层正面从相机视角消失时将不会被绘制。

1.8 扁平化图层
iOS-CALayer (三)_第9张图片
反方向变换的嵌套图层 - Z轴

iOS-CALayer (三)_第10张图片
反方向变换的嵌套图层预期结果 - Y轴

iOS-CALayer (三)_第11张图片
反方向变换的嵌套图层实际效果 - Y轴

所有场景里面绘制的东西不会随着你的观察它的角度改变而发生变化,图层也是一样。不能使用图层树创建一个 3D 结构的层级关系,因为每个父视图把子视图扁平化了。

1.9 固体对象

可以使用6个单视图搭建起一个正方体。通过旋转方向可查看立体效果。


iOS-CALayer (三)_第12张图片
立体效果

二、专用图层

2.1 CAShapeLayer

CAShapeLayer 是一个通过矢量图形而不是 bitmap 来绘制的图层子类。
指定颜色和线宽等属性,用 CGPath 定义绘制的图形,最后CAShapeLayer就自动渲染出来了。
也可以使用 Core Graphics 向原始 CALayer 绘制路径。但是CAShapeLayer的优点:

  • 快速渲染,CAShapeLayer 使用硬件加速,绘制比Core Graphics 快得多
  • 高效使用内存,CAShapeLayer不像 CALayer 创建图层,无论多大都不会占用太多内存。
  • 不会被图层变价裁减掉,可以在边界外绘制
  • 不会出现像素化,做3D 变换时,不会像普通图层变得像素化。

图层绘制形状,只有一次机会设置 lineWith(线宽)、lineCap(线条结尾样子)、lineJoin(线条节点样子)。如果想其他颜色或风格绘制 就不得不新建图层了。

    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.view.layer addSublayer:shapeLayer];

绘制效果图:


iOS-CALayer (三)_第13张图片
火柴人

圆角
CAShapeLayer为创建圆角视图提供了一个方法,就是CALayer的cornerRadius属性。

//有三个圆角一个直角的矩形
//define path parameters
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];
2.2 CATextLayer

Core Animation提供了一个CALayer的子类CATextLayer,它以图层的形式包含了UILabel几乎所有的绘制特性,并且额外提供了一些新的特性。CATextLayer也要比UILabel渲染得快得多。UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。而CATextLayer使用了Core text,并且渲染得非常快。
UIView 实现 UILabel 效果:

- (void)viewDidLoad
{
  [super viewDidLoad];

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.foregroundColor = [UIColor blackColor].CGColor;
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  textLayer.font = fontRef;
  textLayer.fontSize = font.pointSize;
  CGFontRelease(fontRef);

  //choose some text
  NSString *text = @"Core Animation提供了一个CALayer的子类CATextLayer,它以图层的形式包含了UILabel几乎所有的绘制特性,并且额外提供了一些新的特性。CATextLayer也要比UILabel渲染得快得多。UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。而CATextLayer使用了Core text,并且渲染得非常快。";

  //set layer text
  textLayer.string = text;
}
@end

并且手动修改contentsScale属性,减少像素化。
textLayer.contentsScale = [UIScreen mainScreen].scale;

iOS-CALayer (三)_第14张图片
对比效果

CATextLayer的font属性不是一个UIFont类型,而是一个CFTypeRef类型。这样可以根据你的具体需要来决定字体属性应该是用CGFontRef类型还是CTFontRef类型(Core Text字体)。同时字体大小也是用fontSize属性单独设置的,因为CTFontRef和CGFontRef并不像UIFont一样包含点大小。这个例子会告诉你如何将UIFont转换成CGFontRef。
另外,CATextLayer的string属性并不是你想象的NSString类型,而是id类型。这样你既可以用NSString也可以用NSAttributedString来指定文本了(注意,NSAttributedString并不是NSString的子类)。属性化字符串是iOS用来渲染字体风格的机制,它以特定的方式来决定指定范围内的字符串的原始信息,比如字体,颜色,字重,斜体等。

2.3 CATransformLayer

CATransformLayer 不仅能显示自己的内容,仅当存在一个作用域子图层变换它才真的存在。CATransformLayer平面化它的子图层,能构建一个层级的3D 结构。


iOS-CALayer (三)_第15张图片
变换的立方体
2.4 CAGradientLayer

CAGradientLayer 用来生成两种或更多颜色平滑渐变的。CAGradientLayer绘制使用了硬件加速。

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

    CAGradientLayer *layer = [CAGradientLayer layer];
    layer.frame = CGRectMake(50, 50, 150, 150);
    [self.view.layer addSublayer:layer];
    
    layer.colors = @[(__bridge id)[UIColor greenColor].CGColor,(__bridge id)[UIColor orangeColor].CGColor];
    
    layer.startPoint = CGPointMake(0, 0);
    layer.endPoint = CGPointMake(1, 1);
iOS-CALayer (三)_第16张图片
效果图

多重渐变
layer.colors 可以包含很多颜色,可以使用 locations 属性来调整空间。locations浮点型数值数组(NSNumber包装)。浮点数定义不同颜色的位置。也可以以单位坐标系标定:0.0-1.0.
如果要使用 locations 属性就必须和 colors 数组相匹配。

    CAGradientLayer *layer = [CAGradientLayer layer];
    layer.frame = CGRectMake(50, 50, 150, 150);
    [self.view.layer addSublayer:layer];
    
    layer.colors = @[(__bridge id)[UIColor blueColor].CGColor,(__bridge id)[UIColor greenColor].CGColor,(__bridge id)[UIColor orangeColor].CGColor];
    layer.locations = @[@0.0,@0.25,@0.5];
    
    layer.startPoint = CGPointMake(0, 0);
    layer.endPoint = CGPointMake(1, 1);
iOS-CALayer (三)_第17张图片
左上角多重渐变效果图
2.5 CAReplicatorLayer

高效生成许多相似的图层,绘制一个或多个图层的子图层,在每个复制体上应用不同的变换。

    CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
    replicator.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:replicator];
    
    //configure the replicator
    replicator.instanceCount = 10;
    
    //apply a transform for each instance
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DTranslate(transform, 0, 200, 0);
    transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
    transform = CATransform3DTranslate(transform, 0, -200, 0);
    replicator.instanceTransform = transform;
    
    //apply a color shift for each instance
    replicator.instanceBlueOffset = -0.1;
    replicator.instanceGreenOffset = -0.1;
    
    //create a sublayer and place it inside the replicator
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f);
    layer.backgroundColor = [UIColor whiteColor].CGColor;
    [replicator addSublayer:layer];
iOS-CALayer (三)_第18张图片
效果图

当图层在重复的时候,他们的颜色也在变化:这是用instanceBlueOffset和instanceGreenOffset属性实现的。通过逐步减少蓝色和绿色通道,我们逐渐将图层颜色转换成了红色。

音量震动条

    //复制层
    CAReplicatorLayer *repL = [CAReplicatorLayer  layer];
    repL.frame = self.contentV.bounds;
    [self.contentV.layer addSublayer:repL];
    
    repL.instanceCount = 5;
    repL.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
    //设置复制出来的子层动画的延时执行时长
    repL.instanceDelay = 1;
    
    //创建一个振动条
    CALayer *layer = [CALayer layer];
    layer.backgroundColor = [UIColor redColor].CGColor;
    layer.bounds = CGRectMake(0, 0, 30, 100);
    layer.anchorPoint = CGPointMake(0, 1);
    layer.position = CGPointMake(0, self.contentV.bounds.size.height);
    [repL addSublayer:layer];
    
    //添加动画
    CABasicAnimation *anim = [CABasicAnimation animation];
    anim.keyPath = @"transform.scale.y";
    anim.toValue = @0;
    anim.repeatCount = MAXFLOAT;
    anim.duration = 1;
    anim.autoreverses = YES;
    [layer addAnimation:anim forKey:nil];
iOS-CALayer (三)_第19张图片
音量效果图.gif

反射
CAReplicatorLayer 应用一个负比例变换于一个复制图层,可以创建指定视图的内容镜像图片。
关键代码

    //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 + 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;
2.6 CAScrollLayer

对于一个未转换的图层,它的bounds和它的frame是一样的,frame属性是由bounds属性自动计算而出的,所以更改任意一个值都会更新其他值。
如果想显示大图层的一小部分,或者包含子图层的部分。CAScrollLayer 有 scrollToPoint 方法,自动适应 bounds 的原点以便图层出现滑动的地方。
仿造一个 ScrollView

    //get the offset by subtracting the pan gesture
    //translation from the current bounds origin
    CGPoint offset = self.bounds.origin;
    offset.x -= [recognizer translationInView:self].x;
    offset.y -= [recognizer translationInView:self].y;

    //scroll the layer
    [(CAScrollLayer *)self.layer scrollToPoint:offset];

    //reset the pan gesture translation
    [recognizer setTranslation:CGPointZero inView:self];

CAScrollLayer 实现的滚动视图没有实现任何形式的边界检查(bounds checking)。图层内容有可能会滑出视图的边界并无限滑下去。相当于没有 UIScrollView 中 contentSize 属性。所以当CAScrollLayer滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。

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

  • scrollPoint:方法从图层树中查找并找到第一个可用的CAScrollLayer,然后滑动它使得指定点成为可视的。
  • scrollRectToVisible:方法实现了同样的事情只不过是作用在一个矩形上的。
  • visibleRect属性决定图层(如果存在)的哪部分是当前的可视区域。
2.7 CATiledLayer

当需要绘制很大的图片,采用主线程的 UIImage 的 -imageName/- imageWithContentsOfFile 方法会阻塞主线程,至少会引起动画卡顿现象。
能高效绘制在iOS上的图片也有一个大小限制。所有显示在屏幕上的图片最终都会被转化为OpenGL纹理,同时OpenGL有一个最大的纹理尺寸(通常是20482048,或40964096,这个取决于设备型号)。如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,你仍然会遇到很大的性能问题,因为Core Animation强制用CPU处理图片而不是更快的GPU。
CATiledLayer 的解决方案:将大图分解成小片然后单独按需载入。

小片裁剪
256*256是CATiledLayer的默认小图大小,默认大小可以通过tileSize属性更改。
CATiledLayer和UIScrollView集成在一起。除了设置图层和滑动视图边界以适配整个图片大小,实现-drawLayer:inContext:方法,当需要载入新的小图时,CATiledLayer就会调用到这个方法。

#import "ViewController.h"
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the tiled layer
    CATiledLayer *tileLayer = [CATiledLayer layer];
    tileLayer.frame = CGRectMake(0, 0, 2048, 2048);
    tileLayer.delegate = self; [self.scrollView.layer addSublayer:tileLayer];

    //configure the scroll view
    self.scrollView.contentSize = tileLayer.frame.size;

    //draw layer
    [tileLayer setNeedsDisplay];
}

- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
    //determine tile coordinate
    CGRect bounds = CGContextGetClipBoundingBox(ctx);
    NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
    NSInteger y = floor(bounds.origin.y / layer.tileSize.height);

    //load tile image
    NSString *imageName = [NSString stringWithFormat: @"Snowman_%02i_%02i", x, y];
    NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
    UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];

    //draw tile
    UIGraphicsPushContext(ctx);
    [tileImage drawInRect:bounds];
    UIGraphicsPopContext();
}
@end

iOS-CALayer (三)_第20张图片
效果图

当滑动这个图片,当CATiledLayer载入小图的时候,会淡入到界面中。是CATiledLayer的默认行为。可以用fadeDuration属性改变淡入时长或直接禁用掉。
CATiledLayer(不同于大部分的UIKit和Core Animation方法)支持多线程绘制,-drawLayer:inContext:方法可以在多个线程中同时地并发调用,小心谨慎地确保你在这个方法中实现的绘制代码是 线程安全的。

Retina 小图
需要设置图层的contentsScale来匹配UIScreen的scale属性:
tileLayer.contentsScale = [UIScreen mainScreen].scale;

tileSize是以像素为单位不是点,增大了contentsScale就自动有了默认的小图尺寸(现在它是128128的点而不是256256).不需要手工更新小图的尺寸或是在Retina分辨率下指定一个不同的小图。需要做的是适应小图渲染代码以对应安排scale的变化,然而:

//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);

通过上述方法纠正scale也意味着雪人图将以一半的大小渲染在Retina设备上(总尺寸是10241024,而不是20482048)。这个通常都不会影响到用CATiledLayer正常显示的图片类型(比如照片和地图,他们在设计上就是要支持放大缩小,能够在不同的缩放条件下显示)。

2.8 CAEmitterLayer

CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
CAEmitterLayer看上去像是许多CAEmitterCell的容器,CAEmitierCell定义了一个例子效果。不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:contents属性可以定义为一个CGImage,其他设置属性控制着表现和行为。

- (void)createCAEmitterLayer{
    
    CAEmitterLayer *layer = [CAEmitterLayer layer];
    layer.frame = self.view.frame;
    [self.view.layer addSublayer:layer];
    
    layer.renderMode = kCAEmitterLayerAdditive;
    layer.emitterPosition = CGPointMake(layer.bounds.size.width*0.5, layer.bounds.size.width*0.5);
    
    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    cell.contents = (__bridge id)[UIImage imageNamed:@"fk_logo"].CGImage;
    cell.birthRate = 20;
    cell.lifetime = 5.0;
    cell.color = [UIColor colorWithRed:0.1 green:0.5 blue:1 alpha:1].CGColor;
    cell.alphaSpeed = -0.4;
    cell.velocity = 50;
    cell.velocityRange = 50;
    cell.emissionRange = M_PI * 2.0;
    
    layer.emitterCells = @[cell];
    
}
iOS-CALayer (三)_第21张图片
效果图.gif

CAEMitterCell的属性:

  • 粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。
  • 粒子某一属性的变化范围。比如emissionRange属性的值是2π,粒子可以从360度任意位置反射出来。如果指定一个小一些的值,可以创造出一个圆锥形。
  • 指定值在时间线上的变化。将alphaSpeed设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐小时的效果。

CAEmitterLayer的属性它自己控制着整个粒子系统的位置和形状。
一些属性比如birthRate,lifetime和celocity,这些属性会以相乘的方式作用在一起,可以用一个值来加速或者扩大整个粒子系统。

其他属性

  • preservesDepth,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层
  • renderMode,控制着在视觉上粒子图片是如何混合的。kCAEmitterLayerAdditive:合并例子重叠部分的亮度使得看上去更亮。默认的kCAEmitterLayerUnordered,效果没那么好看。
2.9 CAEAGLLayer

iOS 处理高性能图片绘制,就要使用 OpenGL,和 Core Animation 和 UIKit 相比,复杂的很。
OpenGL 提供了 Core Animation 的基础,底层是 C 的接口,直接和 iPhone/iPad 硬件通信。OpenGL没有对象和图层的继承概念,简单的处理三角形。OpenGL 所有东西都是3D 空间中又的颜色和纹理。利用OpenGL,你可以绘制任何你知道必要的集合信息和形状逻辑的内容。
在iOS 5中,苹果引入了一个新的框架叫做GLKit,去掉了一些设置OpenGL的复杂性,提供CLKView的UIView的子类,处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要用CAEAGLLayer完成,它是CALayer的一个子类,用来显示任意的OpenGL图形。
尽管不需要GLKit也可以做到这一切,但GLKit囊括了很多额外的工作,比如设置顶点和片段着色器,这些都以类C语言叫做GLSL自包含在程序中,同时在运行时载入到图形硬件中。编写GLSL代码和设置EAGLayer没有关系,用GLKBaseEffect类将着色逻辑抽象出来。

#import "ViewController.h"
#import 
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *glView;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint framebuffer;
@property (nonatomic, assign) GLuint colorRenderbuffer;
@property (nonatomic, assign) GLint framebufferWidth;
@property (nonatomic, assign) GLint framebufferHeight;
@property (nonatomic, strong) GLKBaseEffect *effect;

@end

@implementation ViewController

- (void)setUpBuffers
{
    //set up frame buffer
    glGenFramebuffers(1, &_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);

    //set up color render buffer
    glGenRenderbuffers(1, &_colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
    [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);

    //check success
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
}

- (void)tearDownBuffers
{
    if (_framebuffer) {
        //delete framebuffer
        glDeleteFramebuffers(1, &_framebuffer);
        _framebuffer = 0;
    }

    if (_colorRenderbuffer) {
        //delete color render buffer
        glDeleteRenderbuffers(1, &_colorRenderbuffer);
        _colorRenderbuffer = 0;
    }
}

- (void)drawFrame {
    //bind framebuffer & set viewport
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glViewport(0, 0, _framebufferWidth, _framebufferHeight);

    //bind shader program
    [self.effect prepareToDraw];

    //clear the screen
    glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);

    //set up vertices
    GLfloat vertices[] = {
        -0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
    };

    //set up colors
    GLfloat colors[] = {
        0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
    };

    //draw triangle
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    //present render buffer
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    //set up context
    self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.glContext];

    //set up layer
    self.glLayer = [CAEAGLLayer layer];
    self.glLayer.frame = self.glView.bounds;
    [self.glView.layer addSublayer:self.glLayer];
    self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};

    //set up base effect
    self.effect = [[GLKBaseEffect alloc] init];

    //set up buffers
    [self setUpBuffers];

    //draw frame
    [self drawFrame];
}

- (void)viewDidUnload
{
    [self tearDownBuffers];
    [super viewDidUnload];
}

- (void)dealloc
{
    [self tearDownBuffers];
    [EAGLContext setCurrentContext:nil];
}
@end
iOS-CALayer (三)_第22张图片
效果图

在OpenGL应用中,可能会用NSTimer或CADisplayLink周期性地每秒钟调用-drawRrame方法60次,同时会将几何图形生成和绘制分开以便不会每次都重新生成三角形的顶点(绘制其他的一些东西而不是一个三角形而已)。

2.10 AVPlayerLayer

AVPlayerLayer 是 AVFoundation 提供的。AVPlayerLayer是用来在iOS上播放视频的。它是高级接口例如MPMoivePlayer的底层实现,提供了显示视频的底层控制。
AVPlayerLayer的使用简单:用 +playerLayerWithPlayer: 方法创建一个已经绑定了视频播放器的图层,或先创建一个图层,用player属性绑定一个AVPlayer实例。

    //get video URL
    NSURL *URL = [[NSBundle mainBundle] 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];
iOS-CALayer (三)_第23张图片
播放示意图

代码创建了一个AVPlayerLayer,添加到了一个容器视图中,不是直接在controller中的主视图上添加。为了可以使用自动布局限制使得图层在最中间;否则,一旦设备被旋转要手动重新放置位置,因为Core Animation并不支持自动大小和自动布局。

边框和圆角

    //set player layer frame and attach it to our view
    playerLayer.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:playerLayer];

    //transform layer
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0 / 500.0;
    transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
    playerLayer.transform = transform;
    
    //add rounded corners and border
    playerLayer.masksToBounds = YES;
    playerLayer.cornerRadius = 20.0;
    playerLayer.borderColor = [UIColor redColor].CGColor;
    playerLayer.borderWidth = 5.0;

    //play the video
    [player play];
iOS-CALayer (三)_第24张图片
效果图
下一篇:iOS-CALayer (四)

你可能感兴趣的:(iOS-CALayer (三))