玩轉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


最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索