IOS闲时技术

第一篇:CALayer基础知识

2018-01-22  本文已影响24人  意一ineyee

参考书籍:iOS核心动画

1.png

我们打开QuartzCore框架QuartzCore.h文件会发现其中只包含了<QuartzCore/CoreAnimation.h>一个头文件,也就是说QuartzCoreCoreAnimation可以看做是一对同义词,iOS设备的所有视觉反馈都是由这个框架来支持的。

当然Core Animation翻译过来是核心动画,这个名字听起来多少让人有点误解,会以为它仅仅是用来做动画的,但其实动画仅仅是Core Animation的一部分特性,CALayer及CALayer的呈现能力也属于Core Animation的内容。

所以这次将从Core Animation的呈现特性(CALayer及CALayer的呈现能力,前五篇)Core Animation的动画特性(后四篇)两个方面来学习下Core Animation。

捎带提一下:


目录

一、iOS中的坐标系统

1、坐标系与单位
2、CALayer和UIView的三大布局属性
3、CALayer和UIView的坐标转换

二、CALayer和UIView的联系和区别

1、CALayer和UIView的联系
2、CALayer和UIView的区别

三、CALayer的寄宿图属性

1、contents和contentsScale属性
2、contentsGravity和masksToBounds属性
3、contentsRect属性
4、contentsCenter属性

一、iOS中的坐标系统

1、坐标系与单位

iOS中的坐标系是以左上角作为(0,0)点的,常用的单位有点(point)、像素(pixel)和单位坐标(0~1的一个值),下面将具体介绍这三个单位。

点(point):就是我们常说的开发分辨率,我们开发的时候用的就是这个单位。
像素(pixel):就是我们常说的物理分辨率,就是机子的分辨率,UI给我们切图的时候用的是这个单位。
单位坐标(0~1的一个值):单位坐标不再是一个具体的数值,而是一个0~1之间的数,代表的是一个比例,因此这种方式在某些情况下会很方便。

在iPhone4之前,开发分辨率和物理分辨率是一样的,所以1个点就是1个像素,我们只需要用1x的图片就可以了。但是iPhone4之后采用了Retina显示屏,1个点不再是1个像素,而是2x2个像素,所以要用2x的图片来保证和普通屏幕上的显示效果一样。iPhone-Plus和iPhoneX上的一个点则是3x3个像素,所以要用3x的图片。以下列出了各机型的开发分辨率和物理分辨率:

机型 开发分辨率(pt) 物理分辨率(px)
iPhone3G 320x480 320x480
iPhone4 320x480 640x960
iPhone5 320x568 640x1136
iPhone6 375x667 750x1334
iPhone-Plus 414x736 1242x2208
iPhoneX 375x812 1125x2436
2、CALayer和UIView的三大布局属性
UIView CALayer
frame(相对于父视图的坐标) frame(相对于父图层的坐标)
bounds(相对于视图本身的坐标,因此buonds的x和y值总是0) bounds(相对于图层本身的坐标,因此buonds的x和y值总是0)
center(视图的中心点) position(图层的中心点)
-- anchorPoint(CALayer其实比UIView多出来一个anchorPoint属性来改变它的布局,比如做时钟的指针时就得通过改变layer的anchorPoint才能达到想要的结果)

从大的方面来说:UIView的frame、bounds和center仅仅是CALayer的frame、bounds和position的存取方法,当我们操作UIView的frame、bounds和center时,本质其实是在操作该view所关联的layer的frame、bounds和position。

从小的方面来说:UIView和CALayer的frame其实是一个虚拟属性,它其实是根据frame、bounds、center/position、transform属性等计算得来的。因此修改其它几个值frame就会跟着改变,同理修改frame其它几个值也会作出相应地改变。例如(以UIView举例,CALayer同理):

// 变换前
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:redView];

// 变换后
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.frame = CGRectMake(100, 100, 100, 100);
redView.transform = CGAffineTransformMakeRotation(M_PI_4);
[self.view addSubview:redView];
变换前 变换后
frame = {100,100,100,100} frame = {79.3,79.3,141.4,141.4}
bounds = {0,0,100,100} bounds = {0,0,100,100}
center = {150,150} center={150,150}

由此这里的旋转变换我们可以看出:frame其实不是视图本身,而是视图旋转之后覆盖了的矩形区域,也就是说frame的宽和高并不总是和bounds的宽和高相等的。

3、CALayer和UIView的坐标转换

我们知道子视图或子图层都是依据其父视图或父图层来布局的,如果移动了父视图或父图层,那么子视图或子图层也会跟着做相应的变化,因此我们可以利用这个特性将一些子视图或者子图层添加到一个父视图或者父图层上来整体做变化。

因此,我们默认获取到的视图或图层的坐标都是相对于其父视图或父图层的,但有时我们并不总是想要获得这样的坐标,而是希望获得某个视图或图层相对与某个指定的视图或图层的坐标。这种情况下,我们就用到了坐标转换技术(以UIView为例,CALayer同理):

// 方式1:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
greenView.backgroundColor = [UIColor greenColor];
[redView addSubview:greenView];

CGRect newGreenRect = [redView convertRect:greenView.frame toView:self.view];

NSLog(@"%@===%@", NSStringFromCGRect(greenView.frame), NSStringFromCGRect(newGreenRect));


// 方式2:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
greenView.backgroundColor = [UIColor greenColor];
[redView addSubview:greenView];

CGRect newGreenRect = [self.view convertRect:greenView.frame fromView:redView];

NSLog(@"%@===%@", NSStringFromCGRect(greenView.frame), NSStringFromCGRect(newGreenRect));

两种方式的输出都是:{{0, 0}, {50, 50}}==={{100, 100}, {50, 50}。可见我们完成了坐标的转换。下面列出较完整的坐标转换的方法:

UIView CALayer
[view1 convertRect:rect toView:view2];(view1把自己坐标系下的某个rect转换到view2坐标系下,并返回转换后的rect) [layer1 convertRect:rect toLayer:layer2];
[view1 convertRect:rect fromView:view2];(view1把view2坐标系下的某个rect转换到自己坐标系下,并返回转换后的rect) [layer1 convertRect:rect fromLayer:layer2];
[view1 convertPoint:point toView:view2];(view1把自己坐标系下的某个point转换到view2坐标系下,并返回转换后的point) [layer1 convertPoint:point toLayer:layer2];
[view1 convertPoint:point fromView:view2];(view1把view2坐标系下的某个point转换到自己坐标系下,并返回转换后的point) [layer1 convertPoint:point fromLayer:layer2];

二、CALayer和UIView的联系和区别

UIView:我们在开发中显示在屏幕上的东西一般都是UIView或其子类,我们可以在view上显示一些背景色、文本或者图片等内容,还可以做动画。UIView可以拦截我们的用户操作并作出响应。iOS系统为我们提供了一个视图树来维护所有存在于屏幕上的视图。

CALayer:同样地,CALayer的概念类似于UIView,我们同样可以在layer上显示一些背景色、文本或者图片等内容,也可以做动画。但是CALayer并不关心响应者链。iOS系统同样为我们提供了一个图层数来维护所有存在的图层。

1、CALayer和UIView的联系
2、CALayer和UIView的区别

首先声明,大多数情况下我们只需要使用UIView来做开发就够了,因为UIView的很多API更加高级和容易使用,而且还提供了处理用户交互的功能,我们就不必额外维护响应者链。但是在某些情况下,我们还是需要直接使用CALayer来实现某些效果,因为CALayer更加底层,所以它具备了很多UIView不具备的能力。

三、CALayer的寄宿图属性

那么在开始学习CALayer的额外能力之前,我们再看一个CALayer一个好玩的属性,它就是contents,挺有意思的,通过它我们给任意一个UIView直接设置图片,而不是非要UIImageView了,哈哈。

1、contents和contentsScale属性
//
//  ViewController2.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/12/6.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController2.h"

#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController2 ()

@property (strong, nonatomic) CALayer *customLayer;

@end

@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self layoutUI];
}


#pragma mark - layoutUI

- (void)layoutUI {
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self.view.layer addSublayer:self.customLayer];
}


#pragma mark - setter, getter

- (CALayer *)customLayer {
    
    if (_customLayer == nil) {
        
        _customLayer = [[CALayer alloc] init];
        _customLayer.frame = CGRectMake(0, 100, kScreenWidth, 100);
        _customLayer.backgroundColor = [UIColor redColor].CGColor;
        
        // 1、CALayer有一个contents的属性,是一个id类型,但是其实只能给它赋值图片,赋值其它的类型将会是空白没有效果
        _customLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        // 2、contentsScale是一个控制Retina显示屏的参数,默认值为1,代表一个点会显示一个像素;如果值改为2,则每个点会显示2x2个像素,如果改为3,则每个点会显示3x3个像素点;所以我们使用寄宿图属性的时候一定要记得把这个值设为[UIScreen mainScreen].scale,否则图片在Retina显示屏上就会显示出错
        _customLayer.contentsScale = [UIScreen mainScreen].scale;
     }
    
    return _customLayer;
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.png
2、contentsGravity和masksToBounds属性

但是我们看到上面效果中,图片被拉伸了,我们就可以使用contentsGravity属性来设置一下寄宿图的显示模式,并且也可以用masksToBounds属性来决定要不要切边。

UIView CALayer 效果
UIViewContentModeScaleToFill kCAGravityResize 缩放图片,缩放的方式为使得整个图片充满整个imageView,图片会变形
UIViewContentModeScaleAspectFill kCAGravityResizeAspectFill 缩放图片,缩放的方式为等宽高比缩放,然后充满整个 imageView,图片有可能会超出imageView的边界,当然我们可以通过clipsToBounds或者masksToBounds减掉超出边界的部分
UIViewContentModeScaleAspectFit kCAGravityResizeAspect 缩放图片,缩放的方式为等宽高比缩放,然后适应imageView,图片会完整地显示在imageView上,但是有可能图片撑不满imageView而使得imageView留下空白的部分
UIViewContentModeCenter kCAGravityCenter 图片保持原大小不进行缩放,显示在正中间
UIViewContentModeTop kCAGravityTop 类上
UIViewContentModeLeft kCAGravityLeft 类上
UIViewContentModeBottom kCAGravityBottom 类上
UIViewContentModeRight kCAGravityRight 类上
UIViewContentModeTopLeft kCAGravityTopLeft 类上
UIViewContentModeTopRight kCAGravityTopRight 类上
UIViewContentModeBottomLeft kCAGravityBottomLeft 类上
UIViewContentModeBottomRight kCAGravityBottomRight 类上
UIView CALayer
clipsToBounds masksToBounds

修改部分代码如下:

- (CALayer *)customLayer {
    
    if (_customLayer == nil) {
        
        _customLayer = [[CALayer alloc] init];
        _customLayer.frame = CGRectMake(0, 100, kScreenWidth, 100);
        _customLayer.backgroundColor = [UIColor redColor].CGColor;
        
        // 1、CALayer有一个contents的属性,是一个id类型,但是其实只能给它赋值图片,赋值其它的类型将会是空白没有效果
        _customLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        // 2、contentsScale,是一个控制Retina显示屏的参数,默认值为1,代表一个点会显示一个像素;如果值改为2,则每个点会显示2x2个像素,如果改为3,则每个点会显示3x3个像素点;所以我们使用寄宿图属性的时候一定要记得把这个值设为[UIScreen mainScreen].scale,否则图片在Retina显示屏上就会显示出错
        _customLayer.contentsScale = [UIScreen mainScreen].scale;
        // 3、对应于UIViewContentMode,CALayer也有一个属性来决定图片显示的模式,是cententsGravity
        _customLayer.contentsGravity = kCAGravityCenter;
        // 4、对应于UIView的clipsToBounds,CALayer也有一个属性是masksToBounds来切掉超出layer边界以外的部分
        _customLayer.masksToBounds = YES;
     }
    
    return _customLayer;
}
1.png
3、contentsRect属性

我们修改部分代码如下:

- (CALayer *)customLayer {
    
    if (_customLayer == nil) {
        
        _customLayer = [[CALayer alloc] init];
        _customLayer.frame = CGRectMake(0, 100, kScreenWidth, 100);
        _customLayer.backgroundColor = [UIColor redColor].CGColor;
        
        // 1、CALayer有一个contents的属性,是一个id类型,但是其实只能给它赋值图片,赋值其它的类型将会是空白没有效果
        _customLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        // 2、contentsScale,是一个控制Retina显示屏的参数,默认值为1,代表一个点会显示一个像素;如果值改为2,则每个点会显示2x2个像素,如果改为3,则每个点会显示3x3个像素点;所以我们使用寄宿图属性的时候一定要记得把这个值设为[UIScreen mainScreen].scale,否则图片在Retina显示屏上就会显示出错
        _customLayer.contentsScale = [UIScreen mainScreen].scale;
        // 3、对应于UIViewContentMode,CALayer也有一个属性来决定图片显示的模式,是cententsGravity
        _customLayer.contentsGravity = kCAGravityCenter;
        // 4、对应于UIView的clipsToBounds,CALayer也有一个属性是masksToBounds来切掉超出layer边界以外的部分
        _customLayer.masksToBounds = YES;
        
        // 5、contentsRect属性允许我们在layer上显示寄宿图的某一个子区域,它采用的是单位坐标。我们在App中经常用它来载入拼合图片,因为一次性载入一张拼合的大图要比多次载入多张小图在内存占用、渲染性能上都要高。但这个属性也只是对图片有作用,其它类型的对象不起作用
        CALayer *chun = [[CALayer alloc] init];
        chun.frame = CGRectMake(0, 200, 100, 100);
        chun.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        chun.contentsScale = [UIScreen mainScreen].scale;
        chun.contentsGravity = kCAGravityCenter;
        // 截取春
        chun.contentsRect = CGRectMake(0, 0, 0.5, 0.5);
        [self.view.layer addSublayer:chun];
        
        CALayer *xia = [[CALayer alloc] init];
        xia.frame = CGRectMake(100, 200, 100, 100);
        xia.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        xia.contentsScale = [UIScreen mainScreen].scale;
        xia.contentsGravity = kCAGravityCenter;
        // 截取夏
        xia.contentsRect = CGRectMake(0.5, 0, 0.5, 0.5);
        [self.view.layer addSublayer:xia];
        
        CALayer *qiu = [[CALayer alloc] init];
        qiu.frame = CGRectMake(200, 200, 100, 100);
        qiu.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        qiu.contentsScale = [UIScreen mainScreen].scale;
        qiu.contentsGravity = kCAGravityCenter;
        // 截取秋
        qiu.contentsRect = CGRectMake(0, 0.5, 0.5, 0.5);
        [self.view.layer addSublayer:qiu];
        
        CALayer *dong = [[CALayer alloc] init];
        dong.frame = CGRectMake(300, 200, 100, 100);
        dong.contents = (__bridge id _Nullable)([UIImage imageNamed:@"春夏秋冬"].CGImage);
        dong.contentsScale = [UIScreen mainScreen].scale;
        dong.contentsGravity = kCAGravityCenter;
        // 截取冬
        dong.contentsRect = CGRectMake(0.5, 0.5, 0.5, 0.5);
        [self.view.layer addSublayer:dong];
     }
    
    return _customLayer;
}
1.png
4、contentsCenter属性
UIImage CALayer
resizableImage contentsCenter
例如(11, 22, 11, 22)代表上起11左起22下起11右起22的范围隔出来的四个角(左上、右上、左下、右下四个角)是不可被拉伸的 例如(0.25, 0.25, 0.5, 0.5)代表左边起0.25-0.75的范围及上边起0.25~0.75的范围隔出来的四个角(左上、右上、左下、右下四个角)是不可被拉伸的

以聊天气泡举例:

//
//  ViewController2.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/12/6.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController2.h"

#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController2 ()

@property (strong, nonatomic) CALayer *customLayer;

@end

@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self layoutUI];
}


#pragma mark - layoutUI

- (void)layoutUI {
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 原图
    UIImageView *sourceImageView = [[UIImageView alloc] init];
    sourceImageView.frame = CGRectMake(0, 100, kScreenWidth, 100);
    sourceImageView.backgroundColor = [UIColor yellowColor];
    sourceImageView.image = [UIImage imageNamed:@"聊天气泡"];
    sourceImageView.contentMode = UIViewContentModeCenter;
    [self.view addSubview:sourceImageView];
    
    // 通过UIImage的resizableImage方法来设置不可拉伸区域
    UIImageView *qipao = [[UIImageView alloc] init];
    qipao.frame = CGRectMake(0, 250, kScreenWidth, 100);
    qipao.backgroundColor = [UIColor yellowColor];
    // 设置图片不可被拉伸的区域
    UIImage *image = [[UIImage imageNamed:@"聊天气泡"] resizableImageWithCapInsets:UIEdgeInsetsMake(100, 22, 0, 22) resizingMode:(UIImageResizingModeStretch)];
    qipao.image = image;
    [self.view addSubview:qipao];
    
    //6、contentsCenter:对应于UIImage的resizableImage,CALayer的contentsCenter属性也是用来设置图片不可被拉伸的区域,只不过它用的是单位坐标。
    CALayer *qipaoLayer = [[CALayer alloc] init];
    qipaoLayer.frame = CGRectMake(0, 400, kScreenWidth, 100);
    qipaoLayer.backgroundColor = [UIColor yellowColor].CGColor;
    qipaoLayer.contentsScale = [UIScreen mainScreen].scale;
    qipaoLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"聊天气泡"].CGImage);
    // 设置图片不可被拉伸的区域
    qipaoLayer.contentsCenter = CGRectMake(0.25, 1, 0, 0.5);
    [self.view.layer addSublayer:qipaoLayer];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.png
上一篇 下一篇

猜你喜欢

热点阅读