CALayer介绍
CALayer
在概念和UIView
类似,也是一些被层级关系树管理的矩形块,可以包含图片、文字、背景色等内容。和UIView
的最大不同是不能够处理与用户的交互。
每一个UIView
都有个CALayer
实例的图层属性,被称为backing layer,由视图负责创建并管理这个图层,以确保当子视图在层级关系中添加或被移除时,对应的关联图层也有相同的操作。
CALayer的使用
最简单的使用方式
CALayer *redLayer = [CALayer layer];
redLayer.backgroundColor = [UIColor redColor].CGColor;
redLayer.frame = CGRectMake(100, 100, 100, 100);
[self.view.layer addSublayer:redLayer];
复制代码
contents属性
contents
属性被定义为id
类型,意味着它可以是任何类型的对象,实际上给contents
属性赋任何值都是可以编译通过的,但是,在实践中,如果contents
赋值不是CGImage,得到的图层是空白的。
实际上,真正要赋值的类型是CGImageRef
,他是一个指向CGImage结构的指针。UIImage有一个CGImage属性,返回一个CGImageRef
,因为他不是一个Cocoa对象,而是一个CoreFoundation类型,需要通过桥接__bridge关键字进行转换,如下
layer.contents = (__bridge id)(image.CGImage);
复制代码
通过此方法,可以利用CALayer在一个普通的UIView中显示一张图片了。
contentsGravity属性
使用contentsGravity
属性来设置图层中内容在边界中如何对齐,效果等同于UIView的contentMode
。该属性的值都是字符串类型,官方解释如下:
/* A string defining how the contents of the layer is mapped into its bounds rect. Options are
center
,top
,bottom
,left
,right
,topLeft
,topRight
,bottomLeft
,bottomRight
,resize
,resizeAspect
,resizeAspectFill
. The default value isresize
. Note that "bottom" always means "Minimum Y" and "top" always means "Maximum Y". */
使用方式
layer.contentsGravity = kCAGravityCenter;
复制代码
contentsScale属性
使用contentsScale
属性来定义寄宿图的像素尺寸和视图大小的比利,默认值为1.0。属于支持高分辨率(又称为Hi-DPI或Retina)屏幕机制的一部分,用来判断在绘制图层时候为寄宿图创建的空间大小,和需要显示的图片的拉伸度(假设没有设置contentsGravity属性)。设置为1.0,表示将会以每个点1个像素绘制图片,设置为2.0,将以每个点2个像素绘制图片。
使用代码处理寄宿图时,需要手动设置图层的contentsScale,
redLayer.contentsScale = [UIScreen mainScreen].scale;
复制代码
maskToBounds属性
使用该属性来决定是否显示超出边界的内容,类似于UIView的clipsToBounds
属性。
contentsRect属性
使用该属性来显示寄宿图的一个子域,它使用单位坐标,是一个相对值。(点就是虚拟的像素,也成为逻辑像素。在标准设备上,一个点就是一个像素,但在Retain设备上,一个点等于 2 * 2个像素)
默认的contentsRect是{0,0,1,1},意味着整个寄宿图都是可见的。主要应用是图片拼合,因为单张大图比多张小图载入更快,可以有效提高载入性能
contentsCenter
需要注意的是contentsCenter
其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域,默认值是{0,0,1,1}。改变contentsCenter
的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,才能看到效果。
举例说明:将contentsCenter设置为{0.25,0.25,0.5,0.5},效果如下
还可以在IB中直接设置
最终实现的效果和UIImage的resizableImageWithCapInsets:
方法类似。
frame、bounds、position
1、position类似于UIView中的center,表示相对于父图层anchorPoint所在的位置
2、frame是计算属性,是根据bounds,position,transform计算而来的,所以当其中任何一个发生改变时候,他都会发生改变。
如以下代码执行情况为:
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:redView];
redView.transform = CGAffineTransformMakeRotation(M_PI_4);
NSLog(@"frame %@ bounds %@",NSStringFromCGRect(redView.frame),NSStringFromCGRect(redView.bounds));
复制代码
结果为:
frame {{79.289321881345259, 79.289321881345245}, {141.42135623730951, 141.42135623730951}} bounds {{0, 0}, {100, 100}}
复制代码
anchorPoint 锚点
视图的center属性和图层的position属性都指定了anchorPoint相对于父图层的位置。默认情况下,anchorPoint
位于图层的中点,所以图层会以这个点为中心放置。(注:UIView没有暴露锚点的属性,也是视图的position可以被称为center的原因)
锚点anchorPoint
是用单位坐标来描述的,而且x和y的值可以大于1或小于0,使他可以放置在图层范围之外。
举例说明,实现一个简单的钟表:
@interface ViewController ()
@property(nonatomic,strong) UIView *hourView;
@property(nonatomic,strong) UIView *minuteView;
@property(nonatomic,strong) UIView *secondView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor cyanColor];
self.hourView = [[UIView alloc]init];
self.hourView.backgroundColor = [UIColor greenColor];
self.hourView.bounds = CGRectMake(0, 0, 20, 100);
self.hourView.center = self.view.center;
[self.view addSubview:self.hourView];
self.hourView.layer.anchorPoint = CGPointMake(0.5, 0.9);
self.minuteView = [[UIView alloc]init];
self.minuteView.backgroundColor = [UIColor lightGrayColor];
self.minuteView.bounds = CGRectMake(0, 0, 15, 100);
self.minuteView.center = self.view.center;
[self.view addSubview:self.minuteView];
self.minuteView.layer.anchorPoint = CGPointMake(0.5, 0.9);
self.secondView = [[UIView alloc]init];
self.secondView.backgroundColor = [UIColor purpleColor];
self.secondView.bounds = CGRectMake(0, 0, 10, 100);
self.secondView.center = self.view.center;
[self.view addSubview:self.secondView];
self.secondView.layer.anchorPoint = CGPointMake(0.5, 0.9);
NSTimer *timer = [[NSTimer alloc]initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)tick{
NSCalendar *calendar = [[NSCalendar alloc]initWithCalendarIdentifier:NSCalendarIdentifierChinese];
NSDateComponents *components = [calendar components:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:[NSDate date]];
NSLog(@"Tick...hour %ld minute %ld second %ld",(long)components.hour,(long)components.minute,(long)components.second);
CGFloat secondAngle = (components.second / 60.0) * M_PI *2.0;
CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0 + (components.minute / 60.0) * M_PI / 6.0;
self.hourView.transform = CGAffineTransformMakeRotation(hourAngle);
self.minuteView.transform = CGAffineTransformMakeRotation(minuteAngle);
self.secondView.transform = CGAffineTransformMakeRotation(secondAngle);
}
@end
复制代码
实现效果如图:
geometryFlipped
一般来说,在iOS上,一个图层的position是相对于父图层的左上角,但在Mac上,是相对于左下角。那么可以通过将属性geometryFlipped
设置为YES来改变这种情况。如果将一个视图的geometryFlipped
属性设置为YES,那么所有添加到该图层上的图层和视图的frame都将是相对于左下角而言的,
Z坐标轴
UIView
是存在于一个二维坐标系中的,而CALayer
是存在于一个三维空间中的,描述Z轴上的位置有两个属性zPosition
和anchorPointZ
。ZPosition
可以用作变换图层,还有改变图层的显示顺序。
conrnerRadius
可以利用该属性控制图层角的曲率,默认值是0(也就是直角),但这个曲率只会影响背景颜色而不会影响背景图片或者子视图,如果需要截取图层里的东西,需要将masksToBounds
设置为YES。
redLayer.cornerRadius = 30;
redLayer.masksToBounds = YES;
复制代码
图层边框
利用属性borderWidth
和borderColor
设置图层边框。borderWidth
是以点为单位定义边框粗细的浮点数,默认值为0;borderColor
定义边框的颜色,默认是黑色,是CGColorRef
类型
redLayer.borderWidth = 3.0;
redLayer.borderColor = [UIColor yellowColor].CGColor;
复制代码
阴影
设置阴影主要有四个属性来控制,shadowOpacity
、shadowColor
、shadowOffset
、shadowRadius
。
- shadowOpacity
默认值为0,是一个取值在0.0(不可见)和1.0(完全不透明)之间的浮点数。如果将其设置为1.0,在图层的上方将会显示一个有轻微模糊的黑色阴影。
- shadowColor
控制阴影的颜色。默认值是黑色,类型是CGColorRef
- shadowOffset
控制阴影的方向和距离,是一个CGSize类型,宽度控制阴影横向的位移,高度控制纵向的位移。默认值是{0,-3},意思是相对Y轴有3个点的向上位移。
- shadowRadius
控制阴影的模糊度,值越大,边界线看上去就会越来越模糊和自然(最好设置为非零值)。
除此之外,还可以使用shadowPath
设置指定形状的阴影:
redLayer.shadowOpacity = 1.0;
CGMutablePathRef path = CGPathCreateMutable();
// CGPathAddRect(path, NULL, redLayer.bounds);
CGPathAddEllipseInRect(path, NULL, redLayer.bounds);
redLayer.shadowPath = path;
CGPathRelease(path);
复制代码
path路径的生成,如果图形比较复杂,可以使用UIBezierPath
来实现。
mask
使用mask
属性,可以利用一个32位有alpha通道的png图片创建无矩形视图。它本身是一个CALayer类型,和其他视图有一样的绘制和布局属性,类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域,mask图层实心的部分会被保留下来,其他部分被抛弃。
CALayer *mask = [CALayer layer];
mask.frame = redLayer.bounds;
mask.contents = (__bridge id)[UIImage imageNamed:@"circle"].CGImage;
redLayer.mask = mask;
复制代码
CALayer蒙版图层最厉害的地方在于他不限于静态图,任何有图层构成的都可以作为mask属性,意味着蒙版可以通过代码甚至是动画实时生成。
minificationFilter & magnificationFilter
当图片需要显示不同的大小时候,可以使用拉伸过滤算法,他可以作用于原图的像素上并根据需要生成新的像素显示在屏幕上。CALayer提供了三种拉伸过滤方法:kCAFilterLinear
、kCAFilterNearest
、kCAFilterTrilinear
。minification(缩小图片)和magnification(放大图片)默认的过滤器都是kCAFilterLinear
。
一些特定的CALayer使用方式
CAShaperLayer
CAShapLayer是一个通过矢量图形而不是bitmap来绘制的图层子类,可以使用CGPaht来定义想要的任何图形,他可以将其自动渲染出来。
举例说明:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(175, 100)];
[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:(M_PI *2) 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)];
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;
shapeLayer.shadowOpacity = 1.0;
[self.view.layer addSublayer:shapeLayer];
复制代码
实现效果:
还可以利用CAShapeLayer实现指定方向有圆角
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomRight cornerRadii:CGSizeMake(20, 20)];
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;
shapeLayer.shadowOpacity = 1.0;
[self.view.layer addSublayer:shapeLayer];
复制代码
效果如下:
CATextLayer
CATextLayer
使用了CoreText,比使用UILabel渲染要快得多,而且基本包含了UILabel提供的所有绘制特性。 基本使用方式:
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = CGRectMake(100, 100, 200, 80);
[self.view.layer addSublayer:textLayer];
//设置文本内容
textLayer.string = @"子曰:君子食无求饱,居无求安,敏於事而慎於言,就有道而正焉,可谓好学也已。";
//设置背景颜色
textLayer.backgroundColor = [UIColor redColor].CGColor;
//设置字体颜色 默认是白色
textLayer.foregroundColor = [UIColor blackColor].CGColor;
//设置字体 需要将UIFont类型转换成为CGFontRef类型
UIFont *font = [UIFont systemFontOfSize:15];
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;//字体大小需要使用fontSize来进行单独设置,因为CGFontRef类型并不像UIFont一样包含大小
CGFontRelease(fontRef);
//设置字体对齐方式
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
//设置文字分辨率
textLayer.contentsScale = [UIScreen mainScreen].scale;
复制代码
实现效果:
富文本的使用方式:
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = CGRectMake(100, 100, 200, 80);
[self.view.layer addSublayer:textLayer];
textLayer.contentsScale = [UIScreen mainScreen].scale;
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:@"子曰:君子食无求饱,居无求安,敏於事而慎於言,就有道而正焉,可谓好学也已。"];
UIFont *font = [UIFont systemFontOfSize:15];
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, string.length)];
NSDictionary *underlineAttri = @{
(__bridge id)kCTForegroundColorAttributeName : (__bridge id)[UIColor redColor].CGColor,
(__bridge id)kCTFontAttributeName : (__bridge id)fontRef,
(__bridge id)kCTUnderlineStyleAttributeName : @(kCTUnderlineStyleDouble),
};
CFRelease(fontRef);
[string setAttributes:underlineAttri range:NSMakeRange(0, 2)];
textLayer.string = string;
复制代码
实现效果:
CAGradientLayer
使用CAGradientLayer
可以生成两种或多种颜色平滑渐变,它使用硬件加速。
简单的两种颜色的渐变
CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
[self.view.layer addSublayer:layer];
layer.colors = @[
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor,
];
layer.startPoint = CGPointMake(0.5, 0.5);//以单位坐标系进行定义
layer.endPoint = CGPointMake(1.0, 1.0);
复制代码
实现效果:
多重渐变
colors
属性可以包含很多颜色,默认情况下,这些颜色是均匀的被渲染,但也可以使用locations
属性来调整空间。locations
是一个浮点数值的数组,使用NSNumber
进行包装。
注:locations和colors两个数组的大小一定要一致,不然将会得到一个空白的渐变
CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
[self.view.layer addSublayer:layer];
layer.colors = @[
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
];
layer.locations = @[
@(0.0),
@(0.25),
@(0.5),
];
layer.startPoint = CGPointMake(0, 0);//以单位坐标系进行定义
layer.endPoint = CGPointMake(1.0, 1.0);
复制代码
均匀分布效果图:
按照locations分布效果图:
CAReplicatorLayer
利用CAReplicatorLayer
可以高效生成许多类似的图层,他可以绘制一个或多个图层的子图层,并且在每个复制体上应用不同的变换。
重复图层
使用方式:
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 0, 100, 100);
replicatorLayer.position = self.view.center;
[self.view.layer addSublayer:replicatorLayer];
replicatorLayer.backgroundColor = [UIColor redColor].CGColor;
replicatorLayer.instanceCount = 2;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(10, 10, 0);
replicatorLayer.instanceRedOffset = -0.1;
replicatorLayer.instanceBlueOffset = -0.1;
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor whiteColor].CGColor;
[replicatorLayer addSublayer:layer];
复制代码
显示效果:
实现反射效果
可以使用CAReplicatorLayer
并应用一个负比例变换于一个复制图层,可以创建指定视图内容的镜像图片,就实现了反射效果。
封装一个View来实现:
#import "IVReflectionView.h"
@implementation IVReflectionView
+ (Class)layerClass{
return [CAReplicatorLayer class];
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
//this is called when view is created from a nib
[self setup];
}
- (void)setup{
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount = 2;
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;
layer.instanceAlphaOffset = -0.6;
}
@end
复制代码
使用方法:
IVReflectionView *reflectionView = [[IVReflectionView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
reflectionView.backgroundColor = [UIColor redColor];
UIImageView *imageView = [[UIImageView alloc]initWithFrame:reflectionView.bounds];
imageView.image = [UIImage imageNamed:@"media"];
[reflectionView addSubview:imageView];
[self.view addSubview:reflectionView];
复制代码
显示效果:
CAScrollLayer
CAScrollLayer
有一个scrollToPoint:
方法,他能自动适应bounds
的原点以便图层内容出现在滑动的地方,但并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现iOS指定行为例如滑动反弹。
举例说明:将CAScrollLayer
作为视图的宿主图层,创建一个自定义的UIView,然后使用UIPanGestureRecognizer
实现触摸事件响应,运行效果:显示一个大于frame的UIImageView
#import "IVScrollView.h"
@implementation IVScrollView
+ (Class)layerClass{
return [CAScrollLayer class];
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
}
- (void)pan:(UIPanGestureRecognizer *)recognizer{
CGPoint moveBy = [recognizer translationInView:self];
CGPoint origin = self.bounds.origin;
CGFloat offsetX = origin.x - moveBy.x;
CGFloat offsetY = origin.y - moveBy.y;
[(CAScrollLayer *)self.layer scrollPoint:CGPointMake(offsetX, offsetY)];
[recognizer setTranslation:CGPointZero inView:self];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
复制代码
使用方式:
IVScrollView *scrollView = [[IVScrollView alloc]initWithFrame:CGRectMake(0, 100, 300, 300)];
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 500, 500)];
imageView.image = [UIImage imageNamed:@"Accounts"];
[scrollView addSubview:imageView];
复制代码
显示效果:
其他的一些方法和属性
- (void)scrollRectToVisible:(CGRect)r
滑动是的指定区域可见
@property(readonly) CGRect visibleRect;
获取当前的可视区域
CAEmitterLayer
CAEmitterLayer
是一个高性能的粒子引擎,被用来创建实时粒子动画,如:烟雾、火、雨雪等。
使用实例:
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = CGRectMake(100, 100, 200, 200);
[self.view.layer addSublayer:emitter];
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width * 0.5f, emitter.frame.size.height * 0.5f);
CAEmitterCell *cell = [[CAEmitterCell alloc]init];
cell.contents = (__bridge id)[UIImage imageNamed:@"score"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
emitter.emitterCells = @[cell];
复制代码
AVPlayerLayer
需要注意的是,AVPlayerLayer
与其上的Layer不同,并不是CoreAnimation框架的一部分,而是属于AVFoundation框架。可以用来播放视频,是MPMoivePlayer
的底层实现,提供了显示视频的底层控制。
使用方法:
- 直接使用
playerLayerWithPlayer:
方法创建一个已经绑定了视频播放器的图层 - 可以先创建一个图层,然后用
player
属性绑定一个AVPlayer
实例。
举例说明:
因为AVPlayerLayer
是CALayer
的子类,它继承了父类的所有的特性,可以添加边框、蒙版等特性。
NSURL *url = [[NSBundle mainBundle]URLForResource:@"life" withExtension:@"mp4"];
// NSString *path = [[NSBundle mainBundle]pathForResource:@"life" ofType:@"mp4"];
// NSURL *url = [NSURL URLWithString:path]; //这种方式获取的URL无法播放
AVPlayer *player = [AVPlayer playerWithURL:url];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = CGRectMake(100, 100, 200, 200);
[self.view.layer addSublayer:playerLayer];
playerLayer.backgroundColor = [UIColor redColor].CGColor;
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 = 20.0;
playerLayer.borderColor = [UIColor redColor].CGColor;
playerLayer.borderWidth = 5.0f;
[player play];
复制代码
扩展:
导入资源如果无法使用,查看一下设置中的Build Phases > Copy Bundle Resources
是否已经将资源文件添加进去了,如果里面没有,那就需要在这里手动添加了。
CATiledLayer
可以使用CATiledLayer
来绘制一个很大的图片,因为由于内存的限制,将整个图片读取到内存中是很不明智的。
- (void)tiledLayer{
UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
scrollView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:scrollView];
CATiledLayer *tileLayer = [CATiledLayer layer];
tileLayer.frame = CGRectMake(0, 0, 2048, 2048);
tileLayer.delegate = self;
tileLayer.contentsScale = [UIScreen mainScreen].scale;//以屏幕的原生分辨率来渲染
[scrollView.layer addSublayer:tileLayer];
scrollView.contentSize = tileLayer.frame.size;
[tileLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
CGRect bounds = CGContextGetClipBoundingBox(ctx);
UIImage *image = [UIImage imageNamed:@"media"];
UIGraphicsPushContext(ctx);
[image drawInRect:bounds];
UIGraphicsPopContext();
}
复制代码
CATransaction实现动画
先举例说明,改变一个CALayer的backgroundColor,发现并没有立即在屏幕体现出来,而是有一个平滑的过程。
代码:
- (void)changeColor{
self.redLayer = [CALayer layer];
self.redLayer.frame = CGRectMake(100, 100, 100, 100);
[self.view.layer addSublayer:self.redLayer];
self.redLayer.backgroundColor = [UIColor redColor].CGColor;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.redLayer.backgroundColor = [UIColor greenColor].CGColor;
// self.view.layer.backgroundColor = [UIColor greenColor].CGColor;
}
复制代码
效果:(效果展示不太明显,录屏问题)
这就是所谓的隐式动画,由CoreAnimation来决定如何并且何时去做动画。实际上动画的类型由图层行为决定,持续时间是由当前事务来设置的。而CATransaction
就是用来管理事务的类。
CATransaction
没有+alloc
和init
方法,使用+begin
和+commit
进行入栈和出栈管理。
示例,修改动画执行的时间
[CATransaction begin];
[CATransaction setAnimationDuration:2.0];//设置事务的动画时间,默认时间是0.25s
[CATransaction setCompletionBlock:^{
NSLog(@"The Animation finish");
}];//设置动画结束之后的动作
self.redLayer.backgroundColor = [UIColor greenColor].CGColor;
[CATransaction commit];
复制代码
展示效果:
在UIView中有类似方法,
[UIView beginAnimations:@"" context:nil];
[UIView commitAnimations];
复制代码
还有:
[UIView animateWithDuration:1.0 animations:^{
//do something here
}];
复制代码
他们本质上都是由CATransaction
来实现的。
可会发现一个问题,当我们对UIView关联的图层做同样的动画时,会瞬间切换到新值,而不会出现之前的过渡效果,UIView关联图层的动画被禁用了 (注:completionBlock还是会执行的)。
分析原因之前先了解一下隐式动画是如何实现的:
将改变属性时CALayer
自动应用的动画成为行为,当CALayer
的属性被修改时,他会调用-actionForKey:
方法,传递属性的名称,剩下的是下面的几步:
- 图层首先检测他是否有委托,并且是否是实现
CALayerDelegate
协议指定的-actionForLayer:forKey
方法,如果有,则直接调用并返回结果。 - 如果没有委托,或没有实现
-actionForLayer:forKey
方法,图层接着检查包含属性名称对应行为映射的actions
字典。 - 如果
actions
字典没有对应的属性,那么图层接着会在他的style
字典接着搜索属性名 - 最后,如果在
style
里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的defaultActionForKey:
方法。
所以,完成这一轮搜索之后,actionForKey:
要么返回空(这种情况下将不会有动画发生),要么是返回CAAction
协议定义的对象,最后CALayer
那这个结果对先前和当前的值做动画。
这也解释了UIKit是如何禁用隐式动画的:每个UIView对他所关联的图层都扮演一个委托,并且提供了actionForLayer:forKey
的实现方法,当不再一个动画块里实现的时候,UIView对所有的图层行为都返回nil
,但在动画block范围之内时候,他就返回一个非空值。
测试UIView的actionForLayer:forKey:
的实现
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor cyanColor];
self.redView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
self.redView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.redView];
NSLog(@"outside: %@",[self.redView actionForLayer:self.redView.layer forKey:@"backgroundColor"]);
[UIView animateWithDuration:1.0 animations:^{
NSLog(@"inside: %@",[self.redView actionForLayer:self.redView.layer forKey:@"backgroundColor"]);
}];
//另一种实现方式
[UIView beginAnimations:nil context:nil];
NSLog(@"second inside: %@",[self.redView actionForLayer:self.redView.layer forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
复制代码
打印结果为:
iOSFang[921:34878] outside: <null>
iOSFang[921:34878] inside: <CABasicAnimation: 0x600000413d40>
iOSFang[921:34878] second inside: <CABasicAnimation: 0x600000413f80>
复制代码
当然,返回nil
并不是禁用隐式动画的唯一办法,CATransaction
有个方法叫+setDisableActions:
,可以用来对所有属性打开或者关闭隐式动画。
UIView使用动画的方式
- 使用UIView的动画函数
- 继承UIView,覆盖
-actionForLayer:forKey:
方法,或者直接创建一个显示动画
自定义动画行为
- (void)customLayerAnimation{
self.redLayer = [CALayer layer];
self.redLayer.frame = CGRectMake(100, 100, 100, 100);
self.redLayer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:self.redLayer];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.redLayer.actions = @{
@"backgroundColor":transition,
};
}
复制代码
执行效果为:
presentationLayer图层
当改变一个图层的属性时,属性值是立刻更新的,但屏幕上并没有马上发生改变,只是定义了图层动画结束之后的值。
当前显示在屏幕上的属性值记录在一个叫做呈现图层的独立图层中,可以通过presentationLayer
来进行访问。这个呈现图层实际上是模型图层的复制,他的属性值代表了任何指定时刻当前外观效果,可以通过它来获取当前屏幕上真正显示出来的值。在呈现图层上调用modelLayer
获取它所依赖的CALayer(通常返回的是self)。