CALayer 绘图层

1、CALayer 绘图层

  • 在 iOS 系统中,你能看得见摸得着的东西基本上都是 UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是 UIView。其实 UIView 之所以能显示在屏幕上,完全是因为它内部的一个层。在创建 UIView 对象时,UIView 内部会自动创建一个层(即 CALayer 对象),通过 UIView 的 layer 属性可以访问这个层。当 UIView 需要显示到屏幕上时,会调用 drawRect: 方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了 UIView 的显示。换句话说,UIView 本身不具备显示的功能,是它内部的层才有显示功能。

  • CALayer 的简单使用

    • CALayer 是被定义在 QuartzCore 框架中的,通过操作 CALayer 对象,可以很方便地调整 UIView 的一些界面属性,比如:阴影、圆角大小、边框宽度和颜色等。

2、基本绘图层属性设置

  • 1、设置阴影

    1
    2
    3
    4
    self.redView.layer.shadowOpacity = 1;                               // 阴影不透明度
    self.redView.layer.shadowOffset = CGSizeMake(10, 10); // 阴影偏移量
    self.redView.layer.shadowColor = [UIColor yellowColor].CGColor; // 阴影颜色
    self.redView.layer.shadowRadius = 10; // 阴影圆角半径
  • 2、圆角半径

    1
    self.redView.layer.cornerRadius = 75;                               // 主层半径
  • 3、边框

    1
    2
    self.redView.layer.borderWidth = 3;                                 // 边框宽度
    self.redView.layer.borderColor = [UIColor blueColor].CGColor; // 边框颜色
  • 4、imageView 圆角半径设置

    1
    2
    3
    4
    self.imageView.layer.cornerRadius = 75;                             // 主层半径

    // 超出主层边框的内容全部裁剪掉,image 在视图层上
    self.imageView.layer.masksToBounds = YES; // 是否对非主层裁剪
  • 效果

3、形变属性设置

3.1 视图形变

  • 1、单一形变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 旋转
    /*
    (CGFloat angle) 旋转 45 度,需要输入的参数为弧度,45/180 * M_PI,1 度 = PI/180 弧度
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformMakeRotation(0.25 * M_PI);
    }];

    // 缩放
    /*
    (CGFloat sx, CGFloat sy) (1, 2) 宽度和高度的放大倍数
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformMakeScale(1, 2);
    }];

    // 平移
    /*
    (CGFloat tx, CGFloat ty) (100, 100) 水平和垂直方向的移动距离
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformMakeTranslation(100, 100);
    }];
    • 效果

  • 2、 叠加形变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 旋转 + 缩放
    [UIView animateWithDuration:1 animations:^{
    CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI);
    self.redView.transform = CGAffineTransformScale(rotationTransform, 1.5, 1.5);
    }];

    // 旋转 + 平移
    [UIView animateWithDuration:1 animations:^{
    CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI);
    self.redView.transform = CGAffineTransformTranslate(rotationTransform, 100, 0);
    }];

    // 缩放 + 平移
    [UIView animateWithDuration:1 animations:^{
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, 1.5);
    self.redView.transform = CGAffineTransformTranslate(scaleTransform, 100, 0);
    }];

    // 旋转 + 缩放 + 平移
    [UIView animateWithDuration:1 animations:^{
    CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI);
    CGAffineTransform rotationScaleTransform = CGAffineTransformScale(rotationTransform, 1.5, 1.5);
    self.redView.transform = CGAffineTransformTranslate(rotationScaleTransform, 100, 0);
    }];
    • 效果

  • 3、累加形变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 连续旋转
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformRotate(self.redView.transform, 0.25 * M_PI);
    }];

    // 连续缩放
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformScale(self.redView.transform, 1.5, 1.5);
    }];

    // 连续平移
    [UIView animateWithDuration:1 animations:^{
    self.redView.transform = CGAffineTransformTranslate(self.redView.transform, 50, 50);
    }];
    • 效果

  • 4、还原形变

    1
    2
    // 还原所有形变
    self.redView.transform = CGAffineTransformIdentity;

3.2 绘图层形变

  • 1、单一形变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 旋转
    /*
    (CGFloat angle) 旋转 45 度,需要输入的参数为弧度,45/180 * M_PI,1 度 = PI/180 弧度
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.layer.affineTransform = CGAffineTransformMakeRotation(0.25 * M_PI);
    }];

    // 缩放
    /*
    (CGFloat sx, CGFloat sy) (1, 2) 宽度和高度的放大倍数
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.layer.affineTransform = CGAffineTransformMakeScale(1, 2);
    }];

    // 平移
    /*
    (CGFloat tx, CGFloat ty) (100, 100) 水平和垂直方向的移动距离
    */
    [UIView animateWithDuration:1 animations:^{
    self.redView.layer.affineTransform = CGAffineTransformMakeTranslation(100, 100);
    }];

    // 还原所有形变
    self.redView.layer.affineTransform = CGAffineTransformIdentity;
    • 效果

  • 2、快速进行绘图层形变,KVC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 旋转
    [UIView animateWithDuration:1 animations:^{
    [self.redView.layer setValue:@(0.25 * M_PI) forKeyPath:@"transform.rotation"];
    }];

    // 缩放
    [UIView animateWithDuration:1 animations:^{
    [self.redView.layer setValue:@1.5 forKeyPath:@"transform.scale"];
    }];
    • 效果

  • 3、绘图层 3D 形变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 旋转
    /*
    (CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 旋转角度,x y z 轴的坐标,为 0 时在此轴上不旋转
    */
    [UIView animateWithDuration:1 animations:^{
    self.imageView.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
    }];

    // 缩放
    /*
    (CGFloat sx, CGFloat sy, CGFloat sz),x y z 轴的缩放倍数
    */
    [UIView animateWithDuration:1 animations:^{
    self.imageView.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1);
    }];

    // 平移
    /*
    (CGFloat tx, CGFloat ty, CGFloat tz),x y z 轴的平移量
    */
    [UIView animateWithDuration:1 animations:^{
    self.imageView.layer.transform = CATransform3DMakeTranslation(100, 100, 0);
    }];
    • 效果

3.3 获取形变值

  • 获取旋转的角度

    1
    2
    // 根据 transform 获取旋转角度
    CGFloat angle = atan2(self.redView.transform.b, self.redView.transform.a);

4、创建新的绘图层

  • UIView 内部默认有个 CALayer 对象(层),通过 layer 属性可以访问这个层。要注意的是,这个默认的层不允许重新创建,但可以往层里面添加子层。UIView 可以通过 addSubview: 方法添加子视图,类似地,CALayer 可以通过 addSublayer: 方法添加子层。

  • 1、添加一个简单的图层

    1
    2
    3
    4
    5
    6
    7
    // 创建图层
    CALayer *myLayer = [CALayer layer];

    myLayer.frame = CGRectMake(100, 100, 200, 200);
    myLayer.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:myLayer];
    • 效果

  • 2、添加一个显示图片的图层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 创建图层
    CALayer *myLayer = [CALayer layer];

    myLayer.frame = CGRectMake(100, 100, 200, 200);
    myLayer.backgroundColor = [UIColor redColor].CGColor;

    // 设置图层内容
    myLayer.contents = (id)[UIImage imageNamed:@"demo2.jpg"].CGImage;

    [self.view.layer addSublayer:myLayer];
    • 效果

  • 3、为什么 CALayer 中使用 CGColorRef 和 CGImageRef 这 2 种数据类型,而不用 UIColor 和 UIImage ?

    • 首先要知道:CALayer 是定义在 QuartzCore 框架中的;CGImageRef、CGColorRef 两种数据类型是定义在 CoreGraphics 框架中的;UIColor、UIImage 是定义在 UIKit 框架中的。
    • 其次,QuartzCore 框架和 CoreGraphics 框架是可以跨平台使用的,在 iOS 和 Mac OS X 上都能使用,但是 UIKit 只能在 iOS 中使用。
    • 因此,为了保证可移植性,QuartzCore 不能使用 UIImage、UIColor,只能使用 CGImageRef、CGColorRef。
    • 不过很多情况下,可以通过 UIKit 对象的特定方法,得到 CoreGraphics 对象,比如 UIImage 的 CGImage 方法可以返回一个 CGImageRef。
  • 4、UIView 和 CALayer 的选择

    • 细心的朋友不难发现,其实前面的 2 个效果不仅可以通过添加层来实现,还可以通过添加 UIView 来实现。比如,第 1 个红色的层可以用一个 UIView 来实现,第 2 个显示图片的层可以用一个 UIImageView 来实现。既然 CALayer 和 UIView 都能实现相同的显示效果,那究竟该选择谁好呢?
    • 其实,对比 CALayer,UIView 多了一个事件处理的功能。也就是说,CALayer 不能处理用户的触摸事件,而 UIView 可以。
    • 所以,如果显示出来的东西需要跟用户进行交互的话,用 UIView;如果不需要跟用户进行交互,用 UIView 或者 CALayer 都可以。
    • 当然,CALayer 的性能会高一些,因为它少了事件处理的功能,更加轻量级。
  • 5、UIView 和 CALayer 的关系

    • UIView 可以通过 subviews 属性访问所有的子视图,类似地,CALayer 也可以通过 sublayers 属性访问所有的子层。
    • UIView 可以通过 superview 属性访问父视图,类似地,CALayer 也可以通过 superlayer 属性访问父层。
    • 下面再看一张 UIView 和 CALayer 的关系图,如果两个 UIView 是父子关系,那么它们内部的 CALayer 也是父子关系。

5、绘图层隐式动画属性

  • 在前面已经提到,每一个 UIView 内部都默认关联着一个 CALayer,我们可称这个 Layer 为 Root Layer(根层)。所有的非 Root Layer,也就是手动创建的 CALayer 对象,都存在着隐式动画。

  • 当对非 Root Layer 的部分属性进行相应的修改时,默认会自动产生一些动画效果,这些属性称为 Animatable Properties 可动画属性。

  • 列举几个常见的 Animatable Properties:

    1
    2
    3
    4
    5
    6
    bounds			:用于设置 CALayer 的宽度和高度。修改这个属性会产生缩放动画。
    backgroundColor :用于设置 CALayer 的背景色。修改这个属性会产生背景色的渐变动画。
    position :用于设置 CALayer 的位置。修改这个属性会产生平移动画。比如:假设
    一开始 CALayer 的 position 为(100, 100),然后在某个时刻修改为
    (200, 200),那么整个 CALayer 就会在短时间内从 (100, 100) 这个
    位置平移到 (200, 200)
  • 1、隐式动画属性设置

    1
    2
    3
    4
    5
    6
    7
    self.myLayer.bounds = CGRectMake(0, 0, 100, 100);

    self.myLayer.backgroundColor = [UIColor greenColor].CGColor;

    self.myLayer.position = CGPointMake(arc4random_uniform(200) + 20, arc4random_uniform(400) + 50);

    self.myLayer.transform = CATransform3DMakeRotation(arc4random_uniform(360), 0, 0, 1);
    • 效果

  • 2、可以通过动画事务(CATransaction)关闭默认的隐式动画效果。

    1
    2
    3
    4
    5
    6
    [CATransaction begin];
    [CATransaction setDisableActions:YES];

    self.myLayer.position = CGPointMake(10, 10);

    [CATransaction commit];

6、绘图层 position 和 anchorPoint 属性

  • position 和 anchorPoint 属性都是 CGPoint 类型的。

    1
    2
    3
    position    :位置,可以用来设置 CALayer 在父层中的位置,它是以父层的左上角为坐标原点(0, 0)。
    anchorPoint :锚点,称为 "定位点",它决定着 CALayer 身上的哪个点会在 position 属性所指的位置。
    它的 x、y 取值范围都是 0~1,默认值为 (0.5, 0.5)。
  • 1、anchorPoint 为默认值(0.5, 0.5)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CALayer *myLayer = [CALayer layer];

    myLayer.backgroundColor = [UIColor redColor].CGColor;
    myLayer.bounds = CGRectMake(0, 0, 100, 100);

    // 设置层的位置
    myLayer.position = CGPointMake(100, 100);

    [self.view.layer addSublayer:myLayer];
    • 设置了 myLayer 的 position 为(100, 100),又因为 anchorPoint 默认是(0.5, 0.5),所以最后的效果是 myLayer 的中点会在父层的(100, 100)位置。

  • 2、anchorPoint 为(0, 0)

    • 若将 anchorPoint 改为(0, 0),myLayer 的左上角会在(100, 100)位置。

      1
      myLayer.anchorPoint = CGPointMake(0, 0);

  • 3、anchorPoint 为(1, 1)

    • 若将 anchorPoint 改为(1, 1),myLayer 的右下角会在(100, 100)位置。

      1
      myLayer.anchorPoint = CGPointMake(1, 1);

  • 4、anchorPoint 为(0, 1)

    • 将 anchorPoint 改为(0, 1),myLayer 的左下角会在(100, 100)位置。

      1
      myLayer.anchorPoint = CGPointMake(0, 1);

7、自定义绘图层

7.1 自定义绘图层方法 1

  • 创建一个 CALayer 的子类,然后覆盖 drawInContext: 方法,使用 Quartz2D API 进行绘图。

  • QCLayer.h

    1
    2
    3
    @interface QCLayer : CALayer

    @end
  • QCLayer.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @implementation QCLayer

    #pragma mark 绘制一个实心三角形

    - (void)drawInContext:(CGContextRef)ctx {

    // 设置为蓝色
    CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);

    // 设置起点
    CGContextMoveToPoint(ctx, 50, 0);

    // 从 (50, 0) 连线到 (0, 100)
    CGContextAddLineToPoint(ctx, 0, 100);

    // 从 (0, 100) 连线到 (100, 100)
    CGContextAddLineToPoint(ctx, 100, 100);

    // 合并路径,连接起点和终点
    CGContextClosePath(ctx);

    // 绘制路径
    CGContextFillPath(ctx);
    }

    @end
  • ViewController.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    QCLayer *layer = [QCLayer layer];

    // 设置层的宽高
    layer.bounds = CGRectMake(0, 0, 100, 100);

    // 设置层的位置
    layer.position = CGPointMake(100, 100);

    // 开始绘制图层,需要调用这个方法,才会触发 drawInContext: 方法的调用,然后进行绘图
    [layer setNeedsDisplay];

    [self.view.layer addSublayer:layer];
  • 效果

7.2 自定义绘图层方法 2

  • 设置 CALayer 的 delegate,然后让 delegate 实现 drawLayer:inContext: 方法,当 CALayer 需要绘图时,会调用 delegate 的 drawLayer:inContext: 方法进行绘图。
  • 这里要注意的是:不能再将某个 UIView 设置为 CALayer 的 delegate,因为 UIView 对象已经是它内部根层的 delegate,再次设置为其他层的 delegate 就会出问题。UIView 和它内部 CALayer 的默认关系图:

  • 创建新的层,设置 delegate,然后添加到控制器的 view 的 layer 中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CALayer *layer = [CALayer layer];

    // 设置 delegate,这里的 self 是指控制器
    layer.delegate = self;

    // 设置层的宽高
    layer.bounds = CGRectMake(0, 0, 100, 100);

    // 设置层的位置
    layer.position = CGPointMake(100, 100);

    // 开始绘制图层,需要调用这个方法,才会通知 delegate 进行绘图
    [layer setNeedsDisplay];

    [self.view.layer addSublayer:layer];
  • 让 CALayer 的 delegate(前面设置的是控制器)实现 drawLayer:inContext: 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #pragma mark 画一个矩形框

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {

    // 设置蓝色
    CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 1);

    // 设置边框宽度
    CGContextSetLineWidth(ctx, 10);

    // 添加一个跟层一样大的矩形到路径中
    CGContextAddRect(ctx, layer.bounds);

    // 绘制路径
    CGContextStrokePath(ctx);
    }
  • 效果

7.3 UIView 的详细显示过程

  • 当 UIView 需要显示时,它内部的层会准备好一个 CGContextRef(图形上下文),然后调用 delegate(这里就是 UIView)的 drawLayer:inContext: 方法,并且传入已经准备好的 CGContextRef 对象。而 UIView 在 drawLayer:inContext: 方法中又会调用自己的 drawRect: 方法

  • 平时在 drawRect: 中通过 UIGraphicsGetCurrentContext() 获取的就是由层传入的 CGContextRef 对象,在 drawRect: 中完成的所有绘图都会填入层的 CGContextRef 中,然后被拷贝至屏幕。

8、渐变图层

  • 渐变图层 CAGradientLayer : CALayer

  • 添加渐变图层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.imageView.bounds;

    // 设置透明度
    gradientLayer.opacity = 0.5;

    // 设置渐变颜色
    gradientLayer.colors = @[(id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor];

    [self.imageView.layer addSublayer:gradientLayer];
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.imageView.bounds;

    // 设置透明度
    gradientLayer.opacity = 0.5;

    // 设置渐变颜色
    gradientLayer.colors = @[(id)[UIColor redColor].CGColor,
    (id)[UIColor greenColor].CGColor,
    (id)[UIColor yellowColor].CGColor];

    // 设置渐变定位点
    gradientLayer.locations = @[@0.1, @0.4, @0.8];

    // 设置渐变开始点,取值 0~1
    gradientLayer.startPoint = CGPointMake(0, 1);

    [self.imageView.layer addSublayer:gradientLayer];
    • 效果

9、复制图层

  • 复制图层 CAReplicatorLayer : CALayer,可以把图层里面所有子层复制

  • 添加复制图层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    CAReplicatorLayer *repLayer = [CAReplicatorLayer layer];
    repLayer.frame = self.view.bounds;
    [self.view.layer addSublayer:repLayer];

    // 添加子层
    [repLayer addSublayer:self.imageView.layer];

    // 设置有多少个子层,包括原始层
    repLayer.instanceCount = 4;

    // 设置子层偏移量,不包括原始层,相对于原始层 x 偏移
    repLayer.instanceTransform = CATransform3DMakeTranslation(70, 0, 0);

    // 设置子层背景色
    repLayer.instanceColor = [UIColor greenColor].CGColor;

    // 设置子层阴影
    repLayer.instanceAlphaOffset = -0.1;
    repLayer.instanceRedOffset = -0.1;
    repLayer.instanceGreenOffset = -0.1;
    repLayer.instanceBlueOffset = -0.1;

    // 设置子层动画延迟时间,子层有动画时有效
    repLayer.instanceDelay = 0;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    CAReplicatorLayer *repLayer = [CAReplicatorLayer layer];
    repLayer.frame = self.view.bounds;
    [self.view.layer addSublayer:repLayer];

    // 添加子层
    CALayer *layer = [CALayer layer];
    layer.anchorPoint = CGPointMake(0.5, 1);
    layer.position = CGPointMake(100, 300);
    layer.bounds = CGRectMake(0, 0, 30, 150);
    layer.backgroundColor = [UIColor whiteColor].CGColor;
    [repLayer addSublayer:layer];

    // 添加子层动画
    CABasicAnimation *anim = [CABasicAnimation animation];
    anim.keyPath = @"transform.scale.y";
    anim.toValue = @0.1;
    anim.duration = 0.5;
    anim.repeatCount = MAXFLOAT;
    anim.autoreverses = YES;
    [layer addAnimation:anim forKey:nil];

    // 设置子层
    repLayer.instanceCount = 4;
    repLayer.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
    repLayer.instanceDelay = 0.1;
    repLayer.instanceColor = [UIColor greenColor].CGColor;
    repLayer.instanceGreenOffset = -0.3;
    • 效果

文章目录
  1. 1. 1、CALayer 绘图层
  2. 2. 2、基本绘图层属性设置
  3. 3. 3、形变属性设置
    1. 3.1. 3.1 视图形变
    2. 3.2. 3.2 绘图层形变
    3. 3.3. 3.3 获取形变值
  4. 4. 4、创建新的绘图层
  5. 5. 5、绘图层隐式动画属性
  6. 6. 6、绘图层 position 和 anchorPoint 属性
  7. 7. 7、自定义绘图层
    1. 7.1. 7.1 自定义绘图层方法 1
    2. 7.2. 7.2 自定义绘图层方法 2
    3. 7.3. 7.3 UIView 的详细显示过程
  8. 8. 8、渐变图层
  9. 9. 9、复制图层
隐藏目录