专用图层
1. CAShapeLayer
CAShapeLayer
是一个通过矢量图形而不是bitmap来绘制的图层子类. 可以指定绘制颜色, 线宽等属性, 用CGPath来定义想要绘制的图形, 最后CAShapeLayer就会自动渲染出来了. 相比使用 CoreAnimation直接在layer上绘制图形, CAShapeLayer有以下优势:
- 渲染快速. CAShapeLayer使用硬件加速, 绘制同一图形会比用CoreGraphics快很多.
- 高效使用内存. 一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形, 所以无论有多大, 都不会占用太多的内存.
- 不会被图形边缘剪切掉. CAShapeLayer可以在边界之外绘制, 图层路径不会像使用CoreGraphics的普通CALayer一样被剪裁掉.
- 不会出现像素化. 当给CAShapeLayer做3D变换时候, 它不会像一个有寄宿图的普通图层一样变得像素画.
1.1 创建一个CGPath
CAShapeLayer上绘制的形状可以通过CGPath来表示, CGPath对象可以直接创建, 更好的是通过UIBezierPath创建CGPath(这种方法不需要手动释放内存, 方便管理). 图层路径不一定是闭合的. 也可以是开放的.
1.2 创建圆角
前面提到创建圆角视图的方法, 就是CALayer提供的cornerRadius属性, 但是通过CAShapeLayer可以单独制定每个角是否是圆角.
测试代码如下:
// 测试使用CAShapeLayer绘制圆角
TTShapeLayerView *shapeView = [[TTShapeLayerView alloc] initWithFrame:CGRectMake(10, 100, 100, 30)];
[self.view addSubview:shapeView];
UIRectCorner corners = UIRectCornerBottomRight | UIRectCornerTopLeft;
CGSize radii = CGSizeMake(15, 15);
UIBezierPath *bPath = [UIBezierPath bezierPathWithRoundedRect:shapeView.bounds byRoundingCorners:corners cornerRadii:radii];
shapeView.shapeLayer.lineWidth = 1;
shapeView.shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeView.shapeLayer.strokeColor = [UIColor blueColor].CGColor;
shapeView.shapeLayer.path = bPath.CGPath;
效果:
2. CATextLayer
如果需要在图层里面显示文字可以借助图层代理直接使用CoreGraphics写入图层文本内容(UILabel精髓). 如果越过寄宿于图层的视图, 直接在图层上操作, 那家伙是相当相当相当的麻烦繁琐的.
CoreAnimation提供了CALayer的子类: CATextLayer, 它已图层的形式包含了UIlabel几乎所有的特性, 并且提供了一些新的特性.
CATextLayer使用Coretext, 渲染要比UIlabel快的多. 使用时候需要注意设置contentScale属性, 否则在高分辨率屏幕会出现像素化的问题.
CATextLayer的string属性, 是id类型的, 这样你既可以用NSString也可以用NSAttributeString来指定文本, 使用NSAttributeString对象可以方便的在CATextLayer上绘制文本, 并且设置相关的属性. 由于CATextLayer与UlLabel实现机制不同, 所以用CATextLayer和UIlabel渲染出的文本行距和字间距也是不尽相同的. 总的来说差异不大, 但是有差异.
2.1 UILabel的替代品
CATextLayer有比UILabel更好的性能, 但是不能像视图一样自动缩放和自动布局, 一个好的方案是, 自定义Lable, 继承自UILabel, 用CATextLayer作为作为UILabel的寄宿图层. 这样就CATextLayer可以随着视图自动调整大小, 并且也不会有冗余的寄宿图了. 事实上, 这是可以实现的. UIView 有个类方法叫 +layerClass
, 我们可以重写这个方法, 并返回CALayer的子类, 这样UIView初始化时候回调用+layerClass
方法, 并用它返回的类型来创建宿主图层.
例如:
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (CAShapeLayer *)shapeLayer {
return (CAShapeLayer *)self.layer;
}
如果想在app里面充分利用CALayer子类,用+layerClass来创建基于不同图层的视图是一个简单可复用的方法。
3. CAGradientLayer
CAGradientLayer是用来生成两种或者更多颜色平滑渐变的. 用CoreGraphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图上也是可能的, 但是CAGradientLayer的真正好处是绘制使用了硬件加速, 运行效率会更快.
3.1 基础渐变
Demo:
// CAGradientLayer
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
[self.view.layer addSublayer:gradientLayer];
// 设置frame大小
gradientLayer.frame = CGRectMake(120, 100, 100, 100);
// 设置渐变的颜色 颜色必须用 __bridge id 桥接 CGColor, 否则无效
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor purpleColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor];
// 设置开始位置
gradientLayer.startPoint = CGPointMake(0, 0);
// 设置结束位置
gradientLayer.endPoint = CGPointMake(1, 1);
3.2 多重渐变
默认颜色是按照比例均匀的渐变的, 但是我们可以通过一个叫 locations
的数组属性控制不同颜色的位置, 注意 locations中数组大小要和colors数组大小保持一致, 否则得到空白的渐变.
Demo:
// CAGradientLayer
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
[self.view.layer addSublayer:gradientLayer];
// 设置frame大小
gradientLayer.frame = CGRectMake(120, 100, 100, 100);
// 设置渐变的颜色 颜色必须用 __bridge id 桥接 CGColor, 否则无效
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor purpleColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor];
// 设置开始位置
gradientLayer.startPoint = CGPointMake(0, 0);
// 设置结束位置
gradientLayer.endPoint = CGPointMake(1, 1);
// 设置渐变颜色的位置
gradientLayer.locations = @[@0.5, @0.75, @1];
代码如上, 测试发现, 三个数值的意义是, 第一个数值代表, 第一种颜色开始渐变过渡到第二种颜色的单位坐标; 第二个数值代表, 第二种颜色向第一种颜色方向和向第三种颜色方向开始渐变的单位坐标; 第三个数值代表, 第三种颜色开始和第二种颜色过渡渐变的单位坐标.
4. CAReplicatorLayer
顾名思义, 重复图层. CAReplicatorLayer的目的是为了高效生成许多相似的图层.
上代码:
// 重复图层
CAReplicatorLayer *rLayer = [CAReplicatorLayer layer];
rLayer.frame = CGRectMake(0, 220, self.view.bounds.size.width, 350);
[self.view.layer addSublayer:rLayer];
rLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
// 重复次数
rLayer.instanceCount = 10;
//为每个对象注册一个transform
// CATransform3D transform = CATransform3DMakeTranslation(30, 0, 0);
// rLayer.instanceTransform = transform;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, 0, 20, 0);
transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
transform = CATransform3DTranslate(transform, 0, -20, 0);
rLayer.instanceTransform = transform;
rLayer.instanceRedOffset = -0.1;
rLayer.instanceGreenOffset = -0.1;
rLayer.instanceBlueOffset = 0.1;
CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor whiteColor].CGColor;
// layer.frame = CGRectMake(10, 10, 20, 20);
layer.frame = CGRectMake(rLayer.position.x-10, 140, 20, 20);
[rLayer addSublayer:layer];
效果:
刚开始看这一节时候, 看了几遍, 有点不知所云, 上个demo, 然后一切都清晰明了了.
如上代码所示, 先创建一个CAReplicatorLayer实例, 设置相关的参数, 然后被添加到这个实例上面的subLayer就会发生相应的变化.
在项目实战中, 如果我们直接使用CAReplicatorLayer
做所有的操作, 也会出现很多限制, 比如不能自动适配, 不能随意更改大小等, 我认为正确的做法是, 使用CAReplicatorLayer作为一个视图的寄宿layer, 这样我们可以像操作普通视图一样操作CAReplicatorLayer对象, 同时我们可以把CAReplicatorLayer实例相关的变换和操作都封装到自定义的视图中去, 我们只需要向视图中添加视图或者图层, 就会出现我们想要的效果. 代码如下.
// 重复图层
TTReplicatorView *replicatorView = [[TTReplicatorView alloc] initWithFrame:CGRectMake(0, 220, self.view.bounds.size.width, 350)];
[self.view addSubview:replicatorView];
replicatorView.backgroundColor = [UIColor lightGrayColor];
CAReplicatorLayer *rLayer = replicatorView.replicatorLayer;
// 重复次数
rLayer.instanceCount = 10;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, 0, 20, 0);
transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
transform = CATransform3DTranslate(transform, 0, -20, 0);
rLayer.instanceTransform = transform;
rLayer.instanceRedOffset = -0.1;
rLayer.instanceGreenOffset = -0.1;
rLayer.instanceBlueOffset = 0.1;
UIView *subView = [[UIView alloc] init];
[replicatorView addSubview:subView];
subView.frame = CGRectMake(rLayer.position.x-10, 140, 20, 20);
subView.backgroundColor = [UIColor redColor];
测试结论:
- CAReplicatorLayer对象上可以放置其它的视图或者图层对象, 这时候CAReplicatorLayer对象的变换对子视图的子视图一样有相同的效果.
- 如果变换时候CAReplicatorLayer子图层的位置超出了CAReplicatorLayer实例的bounds, 还是会正常展示出来的.
4.1 反射
CAReplicatorLayer图层的一个重要应用就是反射
// 反射
TTReplicatorView *reflectView = [[TTReplicatorView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(replicatorView.frame)+30, CGRectGetWidth(self.view.frame), 200)];
[self.view addSubview:reflectView];
reflectView.layer.borderWidth = 1;
reflectView.layer.borderColor = [UIColor lightGrayColor].CGColor;
reflectView.clipsToBounds = YES;
//
CAReplicatorLayer *reflectLayer = reflectView.replicatorLayer;
reflectLayer.instanceCount = 2;
reflectLayer.instanceAlphaOffset = -0.6;
//
CATransform3D transform2 = CATransform3DMakeTranslation(0, 80, 0);
transform2 = CATransform3DRotate(transform2, M_PI, 1, 0, 0);
reflectLayer.instanceTransform = transform2;
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"111.jpg"]];
[reflectView addSubview:imgView];
imgView.frame = CGRectMake(20, 60, 100, 80);
imgView.clipsToBounds = YES;
imgView.contentMode = UIViewContentModeScaleAspectFill;
测试反射之后发现, CATransform3D作用在CAReplicatorLayer
对象上之后, 并不是想要的那种效果, 目前还没搞明白是为什么..
5. CAScrollLayer
个人觉得, 并没有什么卵用 ......
第一次看CoreAnimation, 也不可能完全看完看懂啊, 这个类就是那个被省略的部分 ~
6. CATransformLayer
http://wiki.jikexueyuan.com/project/ios-core-animation/special-layer.html
正常情况下, 所有的图层都会将它的子图层平面化到一个场景中, 但是 CATransformLayer
并不会平面化它的子图层, 所以如果需要构建有层级关系的结构时候就非常的有用. 详见文档, 不多解释.
7. CATiledLayer
比如有一张超大的图片, 如果将图片一次性完全载入整个图片到内存中有点不靠谱, 或者相当的慢, 会影响性能, 这时候一个好的解决方法就是 CATiledLayer
, 它也可以将大图裁剪成n个小图, 然后载入展示出来,
待看
8. CAEmitterLayer
这是一个高性能的粒子引擎, 可以被用来创建实时粒子动画, 比如: 烟雾, 火, 雨等效果.
待研究
9. CAEAGLLayer
iOS设备上绘图性能最高的就是OpenGL了, 但是它也是不可思议的复杂. iOS5中苹果引入了新的框架, CLKit, 提供一个CLKView
的UIView的子类, 可以处理大部分设置和绘图工作, 但是OpenGL绘图缓冲的底层可配置选项还是要通过CAEAGLLayer来完成, 它是CALayer的子类, 用来显示任意的OpenGL图形.
10. AVPlayerLayer
这个不是CoreAnimation框架的一部分, 但也紧密的联系在一起的. AVPlayerLayer可以用来在iOS设备上播放视频, 它是高级接口 MPMoviePlayer
的底层实现, 提供了显示视频的底层控制. AVPlayerLayer使用起来也是非常简单的, 使用时候需要先导入 #import <AVFoundation/AVFoundation.h>
框架.
待研究
11. 总结
需要记住的是 CALayer的用处是很大的, 而且它并没有为所有可能的场景进行优化, 为了获得CoreAnimation更好的性能, 需要选择使用合适的CALayer子类.
来源:oschina
链接:https://my.oschina.net/u/2501614/blog/734491