CALayer的使用

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 is resize. 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轴上的位置有两个属性zPositionanchorPointZZPosition可以用作变换图层,还有改变图层的显示顺序。

conrnerRadius

可以利用该属性控制图层角的曲率,默认值是0(也就是直角),但这个曲率只会影响背景颜色而不会影响背景图片或者子视图,如果需要截取图层里的东西,需要将masksToBounds设置为YES。

    redLayer.cornerRadius = 30;
    redLayer.masksToBounds = YES;
复制代码

图层边框

利用属性borderWidthborderColor设置图层边框。borderWidth是以点为单位定义边框粗细的浮点数,默认值为0;borderColor定义边框的颜色,默认是黑色,是CGColorRef类型

    redLayer.borderWidth = 3.0;
    redLayer.borderColor = [UIColor yellowColor].CGColor;
复制代码

阴影

设置阴影主要有四个属性来控制,shadowOpacityshadowColorshadowOffsetshadowRadius

  • 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提供了三种拉伸过滤方法:kCAFilterLinearkCAFilterNearestkCAFilterTrilinear。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实例。

举例说明:

因为AVPlayerLayerCALayer的子类,它继承了父类的所有的特性,可以添加边框、蒙版等特性。

    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没有+allocinit方法,使用+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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值