文章分享至我的个人博客: https://cainluo.github.io/14790557329421.html
作者感言
在上一篇文章《Core Animation》CALayer的Transforms中, 我们了解了二维空间和三维空间的一些布局, 还有就是最简单的旋转, 平移之类的, 再来一些就是混合使用的, 这次我们来换个话题.
**最后:** **如果你有更好的建议或者对这篇文章有不满的地方, 请联系我, 我会参考你们的意见再进行修改, 联系我时, 请备注**`Core Animation`**如果觉得好的话, 希望大家也可以打赏一下~嘻嘻~祝大家学习愉快~谢谢~**
简介
Specialized Layers讲得是一些专用的一些图层类, 而不是之前所说的一些用于图片, 颜色之类的, 下面让我们来看看吧~
CAShapeLayer
在之前的文章里, 我们使用过阴影效果, 并且是不使用CGPath情况下去构建形状不同的阴影, 在CALayer中, 有一个子类叫做CAShapeLayer, 它也是可以做到对应的效果. CAShapeLayer是一个通过矢量图形来进行绘制的图层子类, 而并不是使用Bitmap, 当我们指定对应的颜色, 线宽等属性, 就可以使用CGPath来绘制我们想要的形状, 最后CAShapeLayer就自动渲染出来了, 当然你也可以使用Core Graphics直接对一个CALayer进行绘制, 但CAShapeLayer要比Core Graphics直接操作CALayer要好一些, 比如:
- CAShapeLayer使用了硬件加速, 绘制同一图形时会比Core Graphics渲染的快.
- CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形, 所以无论有多大, 都不会占用太多的内存.
- CAShapeLayer和Core Graphics不一样, 它并不会被图层边界给裁剪掉.
- CAShapeLayer不会出现像素化, 这可以提现在, 用CAShapeLayer做3D变换的时候, 不会和普通的图层一样出现像素化.
创建一个CGPath
刚刚说了, CAShapeLayer可以通过CGPath来绘制任意图形, 并且可以设置一些属性, 比如lineWith, lineCap, lineJoin. 我们绘制这个图形的时候, 不一定要闭合, 图层路径也不是绝对, 可以在一个图层上绘制多个不同的图形, 当然, 如果你要想用不同的颜色风格来绘制N个图形, 那你就要准备好多个Layer了. CAShapeLayer是属于CGPathRef类型, 但在实际开发中, 我们是用UIBezierPath来创建图层路径的, 这样子我们就不用考虑人工释放CGPath了, 下面让我们来看Demo吧:
- (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];
}
圆角
之前我们在之前的文章里, 有提到过把一个视图剪切成圆角, 用的就是CALayer的cornerRadius属性, 而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];
}
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;
contentsScale并且并不关心屏幕的拉伸, 因为默认都是1.0f, 所以我们要高清, 那就设置它吧. CATextLayer里的font属性, 其实并不是一个真正的UIFont类型, 而是一个CFTypeRef类型, 这样子就可以根据我们的需求来决定字体的属性到底是用CGFontRef类型还是用Core Text里的CTFontRef类型了, 同时字体大小也是用fontSize属性单独设置的, 因为CTFontRef和CGFontRef和UIFont完全是两回事, 在代码中我们也知道了如何将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;
}
Leading and Kerning
这里有个点, 由于Core Text和WebKit的内部实现机制不同, 用CATextLayer渲染或者是用UILabel渲染文本行距和字距也是不一样的, 这个是由使用字体和字符来决定的, 所以大家如果要使用普通的UILabel和CATextLayer, 就要好好注意一下了.
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];
}
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;
}
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];
}
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];
}
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];
}
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();
}
这里我们注意到, 我默认是用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];
}
这里补充一下知识点, CAEMitterCell基本上可以分为三种:
粒子的某一属性的初始值, 比如:color属性指定了一个图片的混合色, 在Demo当中我们就设置了某个颜色.
粒子某一属性的变化范围, 比如:emissionRange, 在Demo当中, 我们设置为M_PI * 2.0f, 这意味着粒子可以从360°的任意位置反射出来.
粒子在指定值的时间线上的变化, 比如: alphaSpeed, 子啊Demo中, 我们设置为**-0.4f**, 这意味着, 每过一秒, 粒子的透明度就减少0.4, 这样子就有渐渐消失的效果啦. 而CAEmitterLayer它是控制着整个粒子系统的位置和形状, 比如birthRate, lifetime和celocity, 当然, CAEMitterCell也有这些属性, 整个粒子系统都是这些属性以相乘的方式作用在一起, 这样子我们就可以用一个值来加速或者扩大整个粒子系统. 我们还需要知道另外两个比较重要的属性:
preservesDepth: 是否将一个3D的粒子系统平面化到一个图层, 或者可以在3D空间中混合其他图层.
renderMode: 控制着粒子图片在视觉上是如何混合的, 在Demo当中, 我们设置为kCAEmitterLayerAdditive效果, 默认值为kCAEmitterLayerUnordered, 在开发当中需要什么样的效果, 还是得根据需求的来~
CAEAGLLayer
在iOS当中, 如果我们需要高性能的图形绘制, 那肯定是少不了去了解OpenGL, 这里说的是非游戏类的应用哈, 毕竟游戏有属于自己的一套渲染库, 说起OpenGL, 肯定有很多人觉得这个框架很厉害, 的确是的, 因为OpenGL是用C来写的, 直接和硬件进行通信, 但是呢, 也因为是用C所写的, 几乎有没有抽象出来的接口, 如果你要直接使用OpenGL来把图形显示在屏幕上, 那你就需要写非常多的复杂代码, 虽然OpenGL是非常强大的神器, 因为OpenGL是Core Animation和UIKit的基础. 在OpenGL中, 是没有对象和图层继承的概念, 它只是非常简单的去处理三角形, 在OpenGL中, 所有东西都是3D空间中有颜色和纹理的三角形, 感觉灰常的牛逼~ 如果我们要高效的时候Core Animation, 那么我们就需要判断我们需要绘制哪些内容, 比如(矢量图形, 粒子, 文本等等), 但即使是我们选择了合适的图层去呈现这些内容, Core Animation中也不是每个类型的内容都被高度优化过, 所以要想得到高性能的去绘制, 那就比较蛋疼了. 在iOS 5中, 苹果为了解决这些蛋疼的问题, 加入了一个叫做GLKit的库, 它在一定层度上减少了使用OpenGL的复杂度, 提供了一个叫做GLKView的UIView子类, 帮我们处理大部分的设置内容和绘制工作, 有需要了解GLKit的朋友们可以去翻翻官方文档. 即使是如此, 我们还是需要使用到一个叫做CAEAGLLayer的CALayer子类, 酱紫我们才可以用来显示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];
}
如果我们要做一个真正的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];
}
我们知道了AVPlayerLayer是CALayer的子类, 那么它应当也有父类的所有特性, 比如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];
}
总结
好了, 这次我们讲到这里了, 在这章里, 我们认识CALayer的一些子类, 以及它们的一些特性, 方便我们在开发当中实现我们想要的效果时提供了多一些的参考, 但是呢, 这还远远不够, 我们只是初步的去了解这些CALayer子类的皮毛, 单单CATiledLayer和CAEMitterLayer两个子类我们都可以单独抽出来写一长串的东东, 这个还是后面再说吧, 重点是, 我们要记住, CALayer的用处非常之大, 虽然有一些CALayer的子类并没有为所有可能出现的场景进行优化, 这个就要靠我们自己的头脑风暴去思考如何才能更好的去优化了.
工程地址
项目地址: https://github.com/CainRun/CoreAnimation
最后
码字很费脑, 看官赏点饭钱可好
