玩转iOS开发:6.《Core Animation》CALayer的Specialized Layers

文章分享至个人我的博客: https://cainluo.github.io/14790557329421.htmlhtml


做者感言

在上一篇文章《Core Animation》CALayer的Transforms中, 咱们了解了二维空间和三维空间的一些布局, 还有就是最简单的旋转, 平移之类的, 再来一些就是混合使用的, 此次咱们来换个话题.git

** 最后:** ** 若是你有更好的建议或者对这篇文章有不满的地方, 请联系我, 我会参考大家的意见再进行修改, 联系我时, 请备注**`Core Animation`**若是以为好的话, 但愿你们也能够打赏一下~嘻嘻~祝你们学习愉快~谢谢~**

简介

Specialized Layers讲得是一些专用的一些图层类, 而不是以前所说的一些用于图片, 颜色之类的, 下面让咱们来看看吧~github


CAShapeLayer

在以前的文章里, 咱们使用过阴影效果, 而且是不使用CGPath状况下去构建形状不一样的阴影, 在CALayer中, 有一个子类叫作CAShapeLayer, 它也是能够作到对应的效果. CAShapeLayer是一个经过矢量图形来进行绘制的图层子类, 而并非使用Bitmap, 当咱们指定对应的颜色, 线宽等属性, 就可使用CGPath来绘制咱们想要的形状, 最后CAShapeLayer就自动渲染出来了, 固然你也可使用Core Graphics直接对一个CALayer进行绘制, 但CAShapeLayer要比Core Graphics直接操做CALayer要好一些, 好比:微信

  • CAShapeLayer使用了硬件加速, 绘制同一图形时会比Core Graphics渲染的快.
  • CAShapeLayer不须要像普通CALayer同样建立一个寄宿图形, 因此不管有多大, 都不会占用太多的内存.
  • CAShapeLayerCore Graphics不同, 它并不会被图层边界给裁剪掉.
  • CAShapeLayer不会出现像素化, 这能够提如今, 用CAShapeLayer作3D变换的时候, 不会和普通的图层同样出现像素化.

建立一个CGPath

刚刚说了, CAShapeLayer能够经过CGPath来绘制任意图形, 而且能够设置一些属性, 好比lineWith, lineCap, lineJoin. 咱们绘制这个图形的时候, 不必定要闭合, 图层路径也不是绝对, 能够在一个图层上绘制多个不一样的图形, 固然, 若是你要想用不一样的颜色风格来绘制N个图形, 那你就要准备好多个Layer了. CAShapeLayer是属于CGPathRef类型, 但在实际开发中, 咱们是用UIBezierPath来建立图层路径的, 这样子咱们就不用考虑人工释放CGPath了, 下面让咱们来看Demo吧:app

- (void)createPath {
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    [path moveToPoint:CGPointMake(175, 100)];
    [path addQuadCurveToPoint:CGPointMake(100, 500)
                 controlPoint:CGPointMake(250, 600)];
    
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 10;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;
    shapeLayer.path = path.CGPath;
    
    [self.view.layer addSublayer:shapeLayer];
}
复制代码

1
2

圆角

以前咱们在以前的文章里, 有提到过把一个视图剪切成圆角, 用的就是CALayercornerRadius属性, 而CAShapeLayer类也能够提供一样的功能, 虽然代码多了一些, 但也多了一些灵活, 它能够指定单独的指定每一个角. 咱们建立圆角矩形其实就是人工绘制单独的直线和弧度, 但在UIBezierPath中有提供自动绘制圆角矩形的方法, 直接看代码:框架

- (void)viewRoundedCorners {
    
    CGRect rect = CGRectMake(130, 130, 100, 100);
    CGSize radii = CGSizeMake(10, 10);
    
    UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft | UIRectCornerTopLeft;
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
                                               byRoundingCorners:corners
                                                     cornerRadii:radii];
    
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    
    shapeLayer.path = path.CGPath;
    
    [self.view.layer addSublayer:shapeLayer];
}
复制代码

3
4


CATextLayer

若是咱们想在一个图层里显示文字, 咱们能够借助和UILabel同样的方式, 使用Core Graphics在图层上写入内容, 但若是要越过UILabel这些控件, 直接在图层上显示文字的话, 咱们就要为每一个显示文字的图层建立一个图层代理的类, 而且判断哪一个图层须要显示哪一个字符串, 若是再加一些字体, 颜色一些乱七八糟的东西, 那就蛋疼的不要不要的. 好在CALayer里有一个子类, 叫作CATextLayer, 它几乎都包含了UILabel的全部绘制特性, 并且还额外提供了一些新特性, 而且在渲染的速度上, 要比UILabel快的多, 偷偷说个事, 在iOS 6以前, UILabel实际上是经过WebKit来实现绘制的, 因此那时候iOS在渲染文字的时候会有很是大的性能问题, 但CATextLayer使用的是Core Text, 二者以前彻底不一样一个概念. 说那么多废话, 直接上代码吧:布局

- (void)catextLayer {
    
    UIView *labelView = [[UIView alloc] initWithFrame:CGRectMake(30, 100, 300, 300)];
    
    labelView.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:labelView];
    
    CATextLayer *textLayer = [CATextLayer layer];
    textLayer.frame = labelView.bounds;
    
    [labelView.layer addSublayer:textLayer];
    
    textLayer.foregroundColor = [UIColor blackColor].CGColor;
    textLayer.alignmentMode = kCAAlignmentJustified;
    textLayer.wrapped = YES;
    
    UIFont *font = [UIFont systemFontOfSize:15];
    
    CFStringRef fontName = (__bridge CFStringRef)font.fontName;
    CGFontRef fontRef = CGFontCreateWithFontName(fontName);
    
    textLayer.font = fontRef;
    textLayer.fontSize = font.pointSize;
    
    CGFontRelease(fontRef);
    
    NSString *text = @"这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字";
    
    textLayer.string = text;
}
复制代码

这里还要多说一句, 若是你发现文本显示的时候出现像素化的时候, 只要加上如下这段代码, 就哦了, 它会以Retina模式来渲染:性能

textLayer.contentsScale = [UIScreen mainScreen].scale;
复制代码

5
6

contentsScale而且并不关心屏幕的拉伸, 由于默认都是1.0f, 因此咱们要高清, 那就设置它吧. CATextLayer里的font属性, 其实并非一个真正的UIFont类型, 而是一个CFTypeRef类型, 这样子就能够根据咱们的需求来决定字体的属性究竟是用CGFontRef类型仍是用Core Text里的CTFontRef类型了, 同时字体大小也是用fontSize属性单独设置的, 由于CTFontRefCGFontRefUIFont彻底是两回事, 在代码中咱们也知道了如何将UIFont转成CGFontRef. 固然CATextLayer里的string属性是id类型, 并非咱们想象中的NSString类型, 由于这样子咱们就能够用NSString也能够用NSAttributedString来指定要显示的文本, 好比指定某段文字的字体, 颜色, 字重, 斜体等等.学习

Rich Text

其实在iOS 6的时候, Apple就已经给了UILabel和其余的UIKit文本视图添加直接的属性, 但事实上, 在iOS 3.2的时候, CATextLayer就已经支持属性化字符串了, 若是你想支持更低版本的iOS那么你可使用CATextLayer, 不须要和更复杂的Core Text打交道, 也省略了使用其余的方法, 但如今又会有哪家公司支持低版本的iOS呢? 但不可以说在新版本的iOSD昂中CATextLayer就无用功了, 这个得看咱们的需求来肯定了. 此次咱们把Core Text, CATextLayer, NSAttributedString三者混在一块儿使用一下~测试

- (void)attributedString {
    
    UIView *labelView = [[UIView alloc] initWithFrame:CGRectMake(0, 200, self.view.frame.size.width, 400)];
    
    [self.view addSubview:labelView];
    
    CATextLayer *textLayer = [CATextLayer layer];
    
    textLayer.frame = labelView.bounds;
    textLayer.contentsScale = [UIScreen mainScreen].scale;
    
    [labelView.layer addSublayer:textLayer];
    
    textLayer.alignmentMode = kCAAlignmentJustified; textLayer.wrapped = YES;
    
    UIFont *font = [UIFont systemFontOfSize:15];

    NSString *text = @"这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字, 这是一段测试的文字";
    
    NSMutableAttributedString *string = nil;
    string = [[NSMutableAttributedString alloc] initWithString:text];
    
    CFStringRef fontName = (__bridge CFStringRef)font.fontName; CGFloat fontSize = font.pointSize;
    CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);
    
    NSDictionary *attribs = @{(__bridge id)kCTForegroundColorAttributeName : (__bridge id)[UIColor blackColor].CGColor,
                              (__bridge id)kCTFontAttributeName: (__bridge id)fontRef};
    
    [string setAttributes:attribs range:NSMakeRange(0, [text length])];
    
    attribs = @{(__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
                (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
                (__bridge id)kCTFontAttributeName: (__bridge id)fontRef};
    
    [string setAttributes:attribs range:NSMakeRange(6, 20)];
    
    CFRelease(fontRef);
    
    textLayer.string = string;
}
复制代码

7
8

Leading and Kerning

这里有个点, 因为Core TextWebKit的内部实现机制不一样, 用CATextLayer渲染或者是用UILabel渲染文本行距和字距也是不同的, 这个是由使用字体和字符来决定的, 因此你们若是要使用普通的UILabelCATextLayer, 就要好好注意一下了.

A UILabel Replacement

此次咱们就本身建立一个属于咱们本身的UILabel, 代替系统的UILabel, 虽然这个类也是继承于UILabel, 但比系统的UILabel的**-drawRect:**方法要快, 来看看代码吧~

#import "CLLabel.h"
#import <QuartzCore/QuartzCore.h>

@implementation CLLabel

+ (Class)layerClass {
    
    return [CATextLayer class];
}

- (CATextLayer *)textLayer {
    
    return (CATextLayer *)self.layer;
}

- (void)setUp {
    
    self.text = self.text;
    self.textColor = self.textColor;
    self.font = self.font;
    
    [self textLayer].wrapped = YES;
    [self.layer display];
}

- (id)initWithFrame:(CGRect)frame {
    
    if (self = [super initWithFrame:frame]) {
        
        [self setUp];
    }
    
    return self;
}

- (void)awakeFromNib {
    
    [self setUp];
}

- (void)setText:(NSString *)text {
    super.text = text;
    
    [self textLayer].string = text;
}

- (void)setTextColor:(UIColor *)textColor {
    super.textColor = textColor;
    
    [self textLayer].foregroundColor = textColor.CGColor;
}

- (void)setFont:(UIFont *)font {
    super.font = font;
    
    CFStringRef fontName = (__bridge CFStringRef)font.fontName;
    CGFontRef fontRef = CGFontCreateWithFontName(fontName);
    
    [self textLayer].font = fontRef;
    [self textLayer].fontSize = font.pointSize;
    
    CGFontRelease(fontRef);
}

@end
复制代码

使用这个自定义的CLLabel, 咱们看看效果

- (void)createCLLabel {
    
    CLLabel *label = [[CLLabel alloc] initWithFrame:CGRectMake(20, 50, 200, 200)];
    
    label.text = @"这是一段很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的测试文字";
    label.textColor = [UIColor blackColor];
    
    [self.view addSubview:label];
}
复制代码

9
10
11


CATransformLayer

在咱们平常开发当中, 若是须要用到3D Layer, 能够用到以前咱们说到的3D Transforms, 可是那样子太麻烦了, 要算一堆东西, 若是有一种Layer能够像玩积木同样, 一个一个的组合成一个3D形状的话, 那该多好~ 其实Apple早就想到了这个问题, 它们提供了CATransformLayer, 就是专门用来给Layer作一个容器, 而后让拼接成一个看起来像3D的同样图形. 咱们来看代码:

- (void)transformLayer {
    
    self.view.backgroundColor = [UIColor grayColor];
    
    CATransform3D transform3DOne = CATransform3DIdentity;
    
    transform3DOne.m34 = -1.0 / 500.0;
    
    self.view.layer.sublayerTransform = transform3DOne;
    
    CATransform3D transform3DTwo = CATransform3DIdentity;
    
    transform3DTwo = CATransform3DTranslate(transform3DTwo, -100, 0, 0);
    
    CALayer *cubeOne = [self cubeWithTransform:transform3DTwo];
    
    [self.view.layer addSublayer:cubeOne];
    
    CATransform3D transform3DThree = CATransform3DIdentity;
    
    transform3DThree = CATransform3DTranslate(transform3DThree, 100, 0, 0);
    transform3DThree = CATransform3DRotate(transform3DThree, -M_PI_4, 1, 0, 0);
    transform3DThree = CATransform3DRotate(transform3DThree, -M_PI_4, 0, 1, 0);
    
    CALayer *cubeTwo = [self cubeWithTransform:transform3DThree];
    
    [self.view.layer addSublayer:cubeTwo];
}

- (CALayer *)layerWithTransform:(CATransform3D)transform {
    
    CALayer *layer = [CALayer layer];
    
    layer.frame = CGRectMake(-50, -50, 100, 100);
    
    CGFloat red = (rand() / (double)INT_MAX);
    CGFloat green = (100000 / (double)INT_MAX);
    CGFloat blue = (rand() / (double)INT_MAX);
    
    layer.backgroundColor = [UIColor colorWithRed:red
                                            green:green
                                             blue:blue
                                            alpha:1.0f].CGColor;
    layer.transform = transform;
    
    return layer;
}

- (CALayer *)cubeWithTransform:(CATransform3D)transform {
    
    // cube
    CATransformLayer *cube = [CATransformLayer layer];
    
    // layer one
    CATransform3D transform3D = CATransform3DMakeTranslation(0, 0, 50);
    [cube addSublayer:[self layerWithTransform:transform3D]];
    
    // layer two
    transform3D = CATransform3DMakeTranslation(50, 0, 0);
    transform3D = CATransform3DRotate(transform3D, M_PI_2, 0, 1, 0);
    [cube addSublayer:[self layerWithTransform:transform3D]];
    
    // layer three
    transform3D = CATransform3DMakeTranslation(0, -50, 0);
    transform3D = CATransform3DRotate(transform3D, M_PI_2, 1, 0, 0);
    [cube addSublayer:[self layerWithTransform:transform3D]];

    // layer five
    transform3D = CATransform3DMakeTranslation(-50, 0, 0);
    transform3D = CATransform3DRotate(transform3D, -M_PI_2, 0, 1, 0);
    [cube addSublayer:[self layerWithTransform:transform3D]];

    // layer six
    transform3D = CATransform3DMakeTranslation(0, 0, -50);
    transform3D = CATransform3DRotate(transform3D, M_PI, 0, 1, 0);
    [cube addSublayer:[self layerWithTransform:transform3D]];

    CGSize containerSize = self.view.bounds.size;
    
    cube.position = CGPointMake(containerSize.width / 2.0,
                                containerSize.height / 2.0);
    
    cube.transform = transform;
    
    return cube;
}
复制代码

12
13
14


CAGradientLayer

Layer中, 有一种颜色平滑渐变的子类, 叫作CAGradientLayer, 虽然用Core Graphics也能够经过一些技巧作到和CAGradientLayer同样的效果, 但CAGradieLayer真正好, 是好在它是用硬件加速来绘制的, 直接来看代码吧:

- (void)gradientLayer {
    
    UIView *view = [[UIView alloc] init];
    
    view.bounds = CGRectMake(0, 0, 200, 200);
    view.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = view.bounds;
    
    // 设置渐变的颜色, 理论上来说是无限添加的
    gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
                             (__bridge id)[UIColor greenColor].CGColor];
    
    gradientLayer.startPoint = CGPointMake(0, 0); // 开始渐变的点
    gradientLayer.endPoint = CGPointMake(1, 1); // 结束渐变的点
    
    gradientLayer.locations = @[@0.0, @0.2]; // 设置渐变的区域
    
    [view.layer addSublayer:gradientLayer];
    
    [self.view addSubview:view];
}
复制代码

15
16


CAReplicatorLayer

CALayer的子类当中还有一个叫作CAReplicatorLayer, 它是用来复制重复的图层, 而且, 你能够给这些复制的图层进行一些属性上的操做, 好比渐变色, 渐变透明, 形状, 还能够加动画效果, 来看看代码吧:

#pragma mark - CAReplicatorLayer
- (void)replicatorLayer {
    
    UIView *view = [[UIView alloc] init];
    
    view.bounds = CGRectMake(0, 0, 100, 100);
    view.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 4.5);
    
    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);

    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    
    replicatorLayer.frame = view.bounds;
    replicatorLayer.instanceCount = 10;  // 复制图层个数
    replicatorLayer.instanceBlueOffset = -1.0f; // 设置每个图层的逐渐蓝色偏移
    replicatorLayer.instanceRedOffset = -1.0f;  // 设置每个图层的逐渐红色偏移
    replicatorLayer.instanceAlphaOffset = -0.1f;
    replicatorLayer.instanceDelay = 0.33f;  // 设置每一个图层延迟0.33f
    replicatorLayer.instanceTransform = transform;
    
    CALayer *layer = [CALayer layer];
    
    layer.frame = CGRectMake(0, 0, 100, 100);
    layer.backgroundColor = [UIColor whiteColor].CGColor;
    
    [replicatorLayer addSublayer:layer];
    
    self.view.backgroundColor = [UIColor grayColor];
    
    [view.layer addSublayer:replicatorLayer];
    
    [self addLayerAnimation:layer];
    
    [self.view addSubview:view];
}

- (void)addLayerAnimation:(CALayer *)layer {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    
    animation.toValue =  @(layer.position.y - 25.0);
    animation.duration = 0.5;
    animation.autoreverses = true;
    animation.repeatCount = CGFLOAT_MAX;
    
    [layer addAnimation:animation forKey:nil];
}
复制代码

17
18

Reflections

CAReplicatorLayer其实还有一个更加实用的功能, 就是作一个镜面反射的效果, 咱们能够本身封装一个UIView的类, 也能够本身写一个简单的, 这里我就写个简单点的吧, 你们也能够去GitHub里面搜搜, 我在网上搜到一个, 虽然这个库已经2年多没更新了, 但仍是值得看看的ReflectionView.

- (void)reflectionsLayer {
    
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    
    replicatorLayer.instanceCount = 2;
    replicatorLayer.frame = CGRectMake(50, 100, 100, 100);

    CALayer *layer = [CALayer layer];
    
    layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"github"].CGImage);
    layer.frame = replicatorLayer.bounds;

    CATransform3D transform = CATransform3DIdentity;
    
    transform = CATransform3DTranslate(transform, 0, layer.bounds.size.height, 0);
    transform = CATransform3DScale(transform, 1, -1, 0);
    
    replicatorLayer.instanceTransform = transform;
    replicatorLayer.instanceAlphaOffset = -0.6;
    
    [replicatorLayer addSublayer:layer];
    
    [self.view.layer addSublayer:replicatorLayer];
    self.view.backgroundColor = [UIColor grayColor];
}
复制代码

19
20


CAScrollLayer

CALayer的子类当中, 还有一个CAScrollLayer, 它能够被称为UIScrollView的代替品, 但有一个问题, 咱们都知道Core Animation是不能处理用户输入, 因此CAScrollLayer也不能处理滑动事件, 也不能实现UIScrollView那种滑动反弹效果, 但这里加了一个滑动手势就能够实现了滑动效果了.

@interface ViewController ()

@property (nonatomic, strong) CAScrollLayer *scrollLayer;

@end

#pragma mark - CAScrollLayer
- (void)addScrollLayer {
    
    CALayer *layer = [CALayer layer];
    
    layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"github"].CGImage);
    layer.frame = CGRectMake(0, 0, 300, 300);
    
    self.scrollLayer = [CAScrollLayer layer];
    self.scrollLayer.frame = CGRectMake(50, 100, 150, 150);
    self.scrollLayer.scrollMode = kCAScrollBoth;
    self.scrollLayer.backgroundColor = [UIColor grayColor].CGColor;
    
    [self.scrollLayer addSublayer:layer];
    
    [self.view.layer addSublayer:self.scrollLayer];
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
    
    [self.view addGestureRecognizer:pan];
}

- (void)panGesture:(UIPanGestureRecognizer *)pan {
    
    CGPoint translocation = [pan translationInView:self.view];
    CGPoint origin = self.scrollLayer.bounds.origin;
    
    origin = CGPointMake(origin.x - translocation.x, origin.y - translocation.y);
    
    [self.scrollLayer scrollToPoint:origin];
    
    [pan setTranslation:CGPointZero inView:self.view];
}
复制代码

CATiledLayer

在咱们开发当中, 有时候咱们会须要加载一张超大的图片, 好比神马4K高清图, 或者是世界地图等等之类的, 可是在iOS当中是有内存限制的, 并不像其余系统同样4G, 6G有超大内存, 若是咱们要把超大的图片加载到内存当中, 那很明显, 直接会撑爆, 或者是加速速度慢得感人, 若是你是在主线程中使用UIImage的**+ (nullable UIImage )imageNamed:(NSString )name;或者是- (nullable instancetype)initWithContentsOfFile:(NSString )path方法来加载图片的话, 那你会惊喜的发现, 卡线程了~~ 在iOS当中, 可以高效的绘制而且加载到界面的图片是有一个大小限制的, 由于在iOS当中全部显示在屏幕上的图片最终都会被转化为OpenGL的纹理, 同时OpenGL是有一个最大纹理尺寸的限制, 根据设备的型号来决定, 一般是20482048或者40964096*, 若是咱们想在单个纹理中显示一个比这个限制尺寸还要大的图, 哪怕图片已经存在于内存当中, 咱们也会遇到很是大的性能问题, 由于Core Animation是强制用CPU处理图片, 而不是GPU, 苹果为了解决这个问题, 因而乎有了CATiledLayer, 下面咱们来看看Demo: 因为我不懂怎么把大图分解成小图, 这里就找张小一点的图用用

- (void)addCATileLayer {
    
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width)];
    
    [self.view addSubview:scrollView];
    
    CATiledLayer *tiledLayer = [CATiledLayer layer];
    
    tiledLayer.frame = CGRectMake(0, 0, 2048, 2048);
    tiledLayer.delegate = self;
    tiledLayer.contentsScale = [UIScreen mainScreen].scale;
    
    [scrollView.layer addSublayer:tiledLayer];
    
    scrollView.contentSize = tiledLayer.frame.size;
    
    [tiledLayer setNeedsDisplay];
}

- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx {
    
    CGRect bounds = CGContextGetClipBoundingBox(ctx);
    
    NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
    NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
    
    NSString *imageName = [NSString stringWithFormat:@"image%02zd_%02zd", x, y];
    
    UIImage *tileImage = [UIImage imageNamed:imageName];
    
    UIGraphicsPushContext(ctx);
    
    [tileImage drawInRect:bounds];
    
    UIGraphicsPopContext();
}
复制代码

21

这里咱们注意到, 我默认是用Retina模式去显示图片的, 因此咱们看起来这些图片会比较小, 若是你不想用Retina模式去显示, 你能够把代码中的一句代码删除便可:

tiledLayer.contentsScale = [UIScreen mainScreen].scale;
复制代码

若是咱们要作到像地图那样子放大缩小的话, 那就要本身头脑风暴一下, 而后想着如何去实现了~~


CAEmitterLayer

iOS 5版本中, 苹果加入了一个新的CALayer子类, 叫作CAEmitterLayer, 它是一个高性能的粒子引擎, 经常使用于制做实时效果的动画, 好比烟雾, 火, 雨等等之类的. 其实仔细想一想, CAEmitterLayer看起来更像是一个容器, 里面装载着不少的CAEmitterCell, 这些CAEmitterCell定义了一个粒子效果, 而后在CAEmitterLayer的装载中显示出来. CAEmitterCell相似于一个普通的CALayer, 它有一个contents的属性, 能够定义为一个CGImage, 但不一样于普通的CALayer的是它有一些课设置属性控制着表现和行为, 想了解更多的话, 你们能够自行去CAEmitterCell的头文件找找, 如今咱们来看看Demo:

- (void)addCAEmitterLayer {
    
    UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 100,
                                                                   self.view.frame.size.width,
                                                                   self.view.frame.size.width)];
    
    [self.view addSubview:contentView];
    
    CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
    
    emitterLayer.frame = contentView.bounds;
    emitterLayer.renderMode = kCAEmitterLayerAdditive;
    emitterLayer.emitterPosition = CGPointMake(emitterLayer.frame.size.width / 2,
                                               emitterLayer.frame.size.height / 2);
    
    [contentView.layer addSublayer:emitterLayer];
    
    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    
    cell.contents = (__bridge id _Nullable)([UIImage imageNamed:@"fire"].CGImage);
    cell.birthRate = 150;
    cell.lifetime = 5.0;
    cell.color = [UIColor colorWithRed:1.f
                                 green:0.5f
                                  blue:0.1f
                                 alpha:1.0f].CGColor;
    cell.alphaSpeed = -0.4f;
    cell.velocity = 50.f;
    cell.velocityRange = 50.f;
    cell.emissionRange = M_PI * 2.0f;
    
    emitterLayer.emitterCells = @[cell];
}
复制代码

22

这里补充一下知识点, CAEMitterCell基本上能够分为三种:

  • 粒子的某一属性的初始值, 好比:color属性指定了一个图片的混合色, 在Demo当中咱们就设置了某个颜色.

  • 粒子某一属性的变化范围, 好比:emissionRange, 在Demo当中, 咱们设置为M_PI * 2.0f, 这意味着粒子能够从360°的任意位置反射出来.

  • 粒子在指定值的时间线上的变化, 好比: alphaSpeed, 子啊Demo中, 咱们设置为**-0.4f**, 这意味着, 每过一秒, 粒子的透明度就减小0.4, 这样子就有渐渐消失的效果啦. 而CAEmitterLayer它是控制着整个粒子系统的位置和形状, 好比birthRate, lifetimecelocity, 固然, CAEMitterCell也有这些属性, 整个粒子系统都是这些属性以相乘的方式做用在一块儿, 这样子咱们就能够用一个值来加速或者扩大整个粒子系统. 咱们还须要知道另外两个比较重要的属性:

  • preservesDepth: 是否将一个3D的粒子系统平面化到一个图层, 或者能够在3D空间中混合其余图层.

  • renderMode: 控制着粒子图片在视觉上是如何混合的, 在Demo当中, 咱们设置为kCAEmitterLayerAdditive效果, 默认值为kCAEmitterLayerUnordered, 在开发当中须要什么样的效果, 仍是得根据需求的来~


CAEAGLLayer

iOS当中, 若是咱们须要高性能的图形绘制, 那确定是少不了去了解OpenGL, 这里说的是非游戏类的应用哈, 毕竟游戏有属于本身的一套渲染库, 提及OpenGL, 确定有不少人以为这个框架很厉害, 的确是的, 由于OpenGL是用C来写的, 直接和硬件进行通讯, 可是呢, 也由于是用C所写的, 几乎有没有抽象出来的接口, 若是你要直接使用OpenGL来把图形显示在屏幕上, 那你就须要写很是多的复杂代码, 虽然OpenGL是很是强大的神器, 由于OpenGLCore AnimationUIKit的基础. 在OpenGL中, 是没有对象和图层继承的概念, 它只是很是简单的去处理三角形, 在OpenGL中, 全部东西都是3D空间中有颜色和纹理的三角形, 感受灰常的牛逼~ 若是咱们要高效的时候Core Animation, 那么咱们就须要判断咱们须要绘制哪些内容, 好比(矢量图形, 粒子, 文本等等), 但即便是咱们选择了合适的图层去呈现这些内容, Core Animation中也不是每一个类型的内容都被高度优化过, 因此要想获得高性能的去绘制, 那就比较蛋疼了. 在iOS 5中, 苹果为了解决这些蛋疼的问题, 加入了一个叫作GLKit的库, 它在必定层度上减小了使用OpenGL的复杂度, 提供了一个叫作GLKViewUIView子类, 帮咱们处理大部分的设置内容和绘制工做, 有须要了解GLKit的朋友们能够去翻翻官方文档. 即便是如此, 咱们仍是须要使用到一个叫作CAEAGLLayerCALayer子类, 酱紫咱们才能够用来显示OpenGL的图形. 这里还须要提到一点, 虽然在大部分状况下, 咱们不须要手动设置CAEAGLLayer(若是是用GLKView的话), 咱们能够设置一个OpenGL ES 2.0的上下文, 这是大多数的用法, GLKit为咱们提供许多便捷的方法, 好比设置顶点和片断的着色器之类的, 这些都是以类C语言叫作GLSL自包含在程序中, 同事在运行时载入到图形硬件中, 固然, GLSL的代码和设置CAEAGLLayer是一毛钱关系都没, 因此咱们会用GLKBaseEffect类, 将着色的逻辑抽象出来就完事, 其余的事情, 仍是和日常使用同样就哦了, 下面让咱们来看看Demo:

- (void)addCAEAGLLayer {
    
    UIView *glView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width)];
    
    [self.view addSubview:glView];
    
    // 设置Context
    self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    [EAGLContext setCurrentContext:self.glContext];
    
    // 设置显示的Layer
    self.glLayer = [CAEAGLLayer layer];
    self.glLayer.frame = glView.bounds;
    [glView.layer addSublayer:self.glLayer];
    self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @NO,
                                        kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};
    
    self.effect = [[GLKBaseEffect alloc] init];
    
    [self setUpBuffers];
    [self drawFrame];
}

- (void)setUpBuffers {
    
    // 设置Frame
    glGenFramebuffers(1, &_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    
    // 设置颜色
    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);
    
    // 检查是否成功
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        
        NSLog(@"%i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
}

- (void)tearDownBuffers {
    
    if (_framebuffer) {
        
        glDeleteFramebuffers(1, &_framebuffer);
        _framebuffer = 0;
    }
    
    if (_colorRenderbuffer) {
        
        glDeleteRenderbuffers(1, &_colorRenderbuffer);
        _colorRenderbuffer = 0;
    }
}

- (void)drawFrame {
    
    // 绑定缓冲区
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glViewport(0, 0, _framebufferWidth, _framebufferHeight);
    
    [self.effect prepareToDraw];
    
    // 清空屏幕
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    
    // 设置顶点
    GLfloat vertices[] = {
        -0.5f, -0.5f, -1.0f,
        0.0f, 0.5f, -1.0f,
        0.5f, -0.5f, -1.0f};
    
    // 设置颜色值
    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};
    
    // 开始画三角形
    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);
    
    // 渲染
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}

- (void)dealloc {
    [self tearDownBuffers];
    
    [EAGLContext setCurrentContext:nil];
}
复制代码

23

若是咱们要作一个真正的OpenGL应用,


AVPlayerLayer

最后一个图层类型叫作AVPlayerLayer, 看名字就知道它并不属于Core Animation里的一个部分, 它是由AVFoundation所提供, 但它和Core Animation紧密的结合在一块儿, 而且是CALayer的子类, 能够用来显示自定义内容. 实际上AVPlayerLayer是用来在iOS上播放视频的, 是属于MPMoivePlayer的底层实现, 提供了显示视频的底层支持. AVPlayerLayer使用起来比较简单, 咱们能够直接来看看Demo:

- (void)addAVPlayerLayer {
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"demo0"
                                         withExtension:@"m4v"];
    
    AVPlayer *player = [AVPlayer playerWithURL:url];
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
    
    playerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    
    [self.view.layer addSublayer:playerLayer];
    
    [player play];
}
复制代码

24

咱们知道了AVPlayerLayerCALayer的子类, 那么它应当也有父类的全部特性, 好比3D, 圆角, 有色边框, 蒙版, 阴影等等效果都有, 咱们再原来的基础上再改改~

- (void)addAVPlayerLayerTwo {
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"demo0"
                                         withExtension:@"m4v"];
    
    AVPlayer *player = [AVPlayer playerWithURL:url];
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
    
    playerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    
    [self.view.layer addSublayer:playerLayer];
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0 / 500.0;
    transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
    
    playerLayer.transform = transform;
    playerLayer.masksToBounds = YES;
    playerLayer.cornerRadius = 30.f;
    playerLayer.borderColor = [UIColor blueColor].CGColor;
    playerLayer.borderWidth = 10.f;
    
    [player play];
}
复制代码

25


总结

好了, 此次咱们讲到这里了, 在这章里, 咱们认识CALayer的一些子类, 以及它们的一些特性, 方便咱们在开发当中实现咱们想要的效果时提供了多一些的参考, 可是呢, 这还远远不够, 咱们只是初步的去了解这些CALayer子类的皮毛, 单单CATiledLayerCAEMitterLayer两个子类咱们均可以单独抽出来写一长串的东东, 这个仍是后面再说吧, 重点是, 咱们要记住, CALayer的用处很是之大, 虽然有一些CALayer的子类并无为全部可能出现的场景进行优化, 这个就要靠咱们本身的头脑风暴去思考如何才能更好的去优化了.


工程地址

项目地址: https://github.com/CainRun/CoreAnimation


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝
相关文章
相关标签/搜索