[iOS UI进阶 - 6.0] CALayer

A.基本知识
1.需要掌握的
  • CALayer的基本属性
  • CALayer和UIView的关系
  • position和anchorPoint的作用
 
2.概念
在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView

其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层

在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层
@property(nonatomic,readonly,retain) CALayer *layer;

当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示

换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能
 
3.基本使用
通过操作CALayer对象,可以很方便地调整UIView的一些外观属性,比如:
阴影
圆角大小
边框宽度和颜色
… …

还可以给图层添加动画,来实现一些比较炫酷的效果
 
4.属性
(1)基本属性
边框颜色(CGColorRef类型)
@property CGColorRef borderColor;

边框宽度
@property CGFloat borderWidth;

圆角半径
@property CGColorRef borderColor;

内容(比如设置为图片CGImageRef)
@property(retain) id contents;
 
(2)transform3d key paths
Image(14)
 
  1 //
  2 //  ViewController.m
  3 //  CALayerTest
  4 //
  5 //  Created by hellovoidworld on 15/1/14.
  6 //  Copyright (c) 2015年 hellovoidworld. All rights reserved.
  7 //
  8 
  9 #import "ViewController.h"
 10 
 11 @interface ViewController ()
 12 @property (weak, nonatomic) IBOutlet UIView *orangeView;
 13 
 14 @end
 15 
 16 @implementation ViewController
 17 
 18 - (void)viewDidLoad {
 19     [super viewDidLoad];
 20     // Do any additional setup after loading the view, typically from a nib.
 21    
 22 //    [self testBasicLayer];
 23    
 24 //    [self testOrangeView];
 25    
 26 //    [self testImageView];
 27    
 28     [self testTransform];
 29 }
 30 
 31 /** 基本属性 */
 32 - (void) testBasicLayer {
 33     //  创建一个图层
 34     CALayer *layer = [[CALayer alloc] init];
 35     // 设置图层边框颜色
 36     layer.borderColor = [UIColor blueColor].CGColor;
 37     //  设置图层边框粗细,边框是占据view内的尺寸的
 38     layer.borderWidth = 10;
 39     //
 40     layer.frame = CGRectMake(50, 100, 200, 200);
 41     layer.backgroundColor = [UIColor redColor].CGColor;
 42     layer.cornerRadius = 15;
 43     //    layer.masksToBounds = YES;
 44    
 45    
 46     //    UIImage *image = [UIImage imageNamed:@"M3Mini"];
 47     //    layer.contents = (id)image.CGImage;
 48    
 49     layer.shadowColor = [UIColor blackColor].CGColor;
 50     layer.shadowOffset = CGSizeMake(50, 50);
 51     layer.shadowOpacity = 0.5;
 52    
 53     [self.view.layer addSublayer:layer];
 54 }
 55 
 56 /** UIView的圆角+阴影 */
 57 - (void) testOrangeView {
 58     self.orangeView.layer.shadowColor = [UIColor blackColor].CGColor;
 59     self.orangeView.layer.shadowOffset = CGSizeMake(20, 20);
 60     self.orangeView.layer.shadowOpacity = 0.5;
 61     self.orangeView.layer.cornerRadius = 15;
 62    
 63     self.orangeView.layer.borderWidth = 10;
 64 }
 65 
 66 /** ImageView的圆角+阴影效果 */
 67 - (void) testImageView {
 68     // 用于产生阴影的辅助view,作为父view,显示在底层
 69     UIView *shadowView = [[UIView alloc] init];
 70     shadowView.frame = CGRectMake(50, 80, 100, 64);
 71     shadowView.layer.cornerRadius = 10;
 72     shadowView.backgroundColor = [UIColor redColor];
 73    
 74     // 产生阴影
 75     shadowView.layer.shadowColor = [UIColor blueColor].CGColor;
 76     shadowView.layer.shadowOffset = CGSizeMake(10, 10);
 77     shadowView.layer.shadowOpacity = 0.7;
 78    
 79     // 用于显示圆角内容的imageView,作为子view,显示在表层
 80     UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"M3Mini"]];
 81     imageView.frame = shadowView.bounds;
 82     imageView.layer.cornerRadius = shadowView.layer.cornerRadius ;
 83    
 84     // 圆角裁剪
 85     imageView.layer.masksToBounds = YES;
 86    
 87     // “合成”两个view
 88     [shadowView addSubview:imageView];
 89    
 90     // 添加到屏幕
 91     [self.view addSubview:shadowView];
 92 }
 93 
 94 /** 3D transform */
 95 - (void) testTransform {
 96     UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"M3Mini"]];
 97     imageView.frame = CGRectMake(50, 80, 100, 64);
 98    
 99     // 1.从3D的x、y、z轴三个向量的缩放
100 //    imageView.layer.transform = CATransform3DMakeScale(2, 2, 0);
101    
102     // 2.沿着x=1,y=1,z=0的向量为轴旋转
103 //    imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 1, 1, 0);
104    
105     // 3.使用key paths设置
106     // 3.1使用整个transform对象来设置,3D向量旋转
107 //    NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_4, 1, 1, 0)];
108 //    [imageView.layer setValue:value forKeyPath:@"transform"];
109    
110     // 3.2直接利用rotation来设置,2D旋转(跟view的transform旋转一样)
111     [imageView.layer setValue:@(M_PI_4) forKeyPath:@"transform.rotation"];
112    
113     [self.view addSubview:imageView];
114 }
115  
116 - (void)didReceiveMemoryWarning {
117     [super didReceiveMemoryWarning];
118     // Dispose of any resources that can be recreated.
119 }
120 
121 @end
 
 
#mark:
1.在同一个View上,使用了layer.masksToBounds=YES;的时候会屏蔽掉阴影效果;如果要同时使用。(注意,同时使用圆角和阴影是允许的)
例如: 要同时实现一个UIImageView的圆角和阴影效果,由于图片所在图层不是UIImageView的主layer,设置的圆角并不能影响到图片(同时阴影也是没有圆角的),所以必须要使用masksToBounds才能实现圆角。
 
单纯同时使用圆角剪裁和阴影:
 1 - (void) testImageView {
 2     UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"M3Mini"]];
 3     imageView.frame = CGRectMake(50, 80, 100, 64);
 4     imageView.layer.cornerRadius = 10;
 5     imageView.layer.masksToBounds = YES;
 6    
 7     imageView.layer.shadowColor = [UIColor blueColor].CGColor;
 8     imageView.layer.shadowOffset = CGSizeMake(10, 10);
 9     imageView.layer.shadowOpacity = 0.7;
10    
11     [self.view addSubview:imageView];
12 }
 
没有阴影,因为被masksToBounds剪掉了:
Image(15)
 
解决:建议使用两个view,用带阴影的view(辅助view)和使用masksToBounds的view(需要显示内容的view)作为父子控件。
 1 - (void) testImageView {
 2     // 用于产生阴影的辅助view,作为父view,显示在底层
 3     UIView *shadowView = [[UIView alloc] init];
 4     shadowView.frame = CGRectMake(50, 80, 100, 64);
 5     shadowView.layer.cornerRadius = 10;
 6     shadowView.backgroundColor = [UIColor redColor];
 7    
 8     // 产生阴影
 9     shadowView.layer.shadowColor = [UIColor blueColor].CGColor;
10     shadowView.layer.shadowOffset = CGSizeMake(10, 10);
11     shadowView.layer.shadowOpacity = 0.7;
12    
13     // 用于显示圆角内容的imageView,作为子view,显示在表层
14     UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"M3Mini"]];
15     imageView.frame = shadowView.bounds;
16     imageView.layer.cornerRadius = shadowView.layer.cornerRadius ;
17    
18     // 圆角裁剪
19     imageView.layer.masksToBounds = YES;
20    
21     // “合成”两个view
22     [shadowView addSubview:imageView];
23    
24     // 添加到屏幕
25     [self.view addSubview:shadowView];
26 }
 
Image(16)
 
 
5.关于CALayer的疑问
首先
CALayer是定义在QuartzCore框架中的
CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的
UIColor、UIImage是定义在UIKit框架中的

其次
QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用
但是UIKit只能在iOS中使用

为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef
 
6.如何选择UIView和CALayer
通过CALayer,就能做出跟UIImageView一样的界面效果

既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?
其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以
当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级
 
7. position & anchorPoint
CALayer有2个非常重要的属性:position和anchorPoint

@property CGPoint position;
用来设置CALayer在父层中的位置
以父层的左上角为原点(0, 0)

@property CGPoint anchorPoint;
称为“定位点”、“锚点”
决定着CALayer身上的哪个点会在position属性所指的位置
以自己的左上角为原点(0, 0)
它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
 
1     CALayer *layer = [[CALayer alloc] init];
2     layer.backgroundColor = [UIColor redColor].CGColor;
3     layer.frame = CGRectMake(0, 0, 100, 100);
4     layer.position = CGPointMake(50, 50);
5     // anchorPoint默认是(0.5,0.5),即中心点
6     layer.anchorPoint = CGPointMake(0,0);
7   
8     [self.view.layer addSublayer:layer];
 
Image(17)
 
8.隐式动画
每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根层)

所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画

什么是隐式动画?
当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果
而这些属性称为Animatable Properties(可动画属性)

列举几个常见的Animatable Properties:
bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
position:用于设置CALayer的位置。修改这个属性会产生平移动画
 
可以通过动画事务(CATransaction)关闭默认的隐式动画效果
1 [CATransaction begin];
2 [CATransaction setDisableActions:YES];
3 self.myview.layer.position = CGPointMake(10, 10);
4 [CATransaction commit];
 
1 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
2     // 缩放
3     self.layer.frame = CGRectMake(self.layer.position.x, self.layer.position.y, 200, 200);
4     // 位移
5     self.layer.position = CGPointMake(200, 200);
6     // 背景色
7     self.layer.backgroundColor = [UIColor blueColor].CGColor;
8 }
 
Image(18)
 
10.自定义图层
有两种方法:自己创建layer类和使用代理
(1)创建继承CALayer的类,并实现drawInContent:方法来绘画
 1 //
 2 //  HVWLayer.m
 3 //  CALayerTest
 4 //
 5 //  Created by hellovoidworld on 15/1/14.
 6 //  Copyright (c) 2015年 hellovoidworld. All rights reserved.
 7 //
 8 
 9 #import "HVWLayer.h"
10 
11 @implementation HVWLayer
12 
13 
14 /**
15 * 必须调用一次layer的setNeedDisplay方法,才能触发此方法
16 */
17 - (void)drawInContext:(CGContextRef)ctx {
18     NSLog(@"%@ - drawInContext", [self class]);
19    
20     // 绿色
21     CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
22     // 矩形
23     CGContextFillRect(ctx, CGRectMake(50, 50, 100, 100));
24     CGContextFillPath(ctx);
25 }
26 
27 @end
 
viewController:
1 - (void) diyLayer1 {
2     HVWLayer *layer = [[HVWLayer alloc] init];
3     layer.frame = CGRectMake(80, 80, 200, 300);
4     layer.backgroundColor = [UIColor grayColor].CGColor;
5     // 必须调用重绘,才能触发layer的drawInContent:方法
6     [layer setNeedsDisplay];
7    
8     [self.view.layer addSublayer:layer];
9 }
 
Image(19)
 
(2)使用layer的delegate
viewController:
 1 - (void) diyLayer2 {
 2     CALayer *layer = [[CALayer alloc] init];
 3     layer.bounds = CGRectMake(0, 0, 200, 300);
 4     layer.anchorPoint = CGPointZero;
 5     layer.backgroundColor = [UIColor grayColor].CGColor;
 6     layer.delegate = self;
 7    
 8     // 必须要进行重绘
 9     [layer setNeedsDisplay];
10    
11     [self.view.layer addSublayer:layer];
12 }
13  
14 #pragma mark - layer代理方法
15 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
16     // 绿色
17     CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
18     // 矩形
19     CGContextFillRect(ctx, CGRectMake(30, 30, 100, 100));
20     CGContextFillPath(ctx);
21 }

 

Image(20)
 
 
11.View和layer的调用关系
(1)view的layer加载顺序
  • view准备一个图层上下文Layer Context Ref
  • 调用[view.layer.delegate drawLayer:inContext:],传入上下文
  • 在drawLayer:inContext:内会调用view的drawRect:方法
  • 所以drawRect:内实现的绘图方法,会画到layer上
  • 系统把layer上的内容描绘到屏幕,完成view的显示
 
view: (创建了一个红圆)
 1 //
 2 //  HVWView.m
 3 //  CALayerTest
 4 //
 5 //  Created by hellovoidworld on 15/1/14.
 6 //  Copyright (c) 2015年 hellovoidworld. All rights reserved.
 7 //
 8 
 9 #import "HVWView.h"
10 
11 @implementation HVWView
12 
13 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
14     NSLog(@"%@ - drawLayer", [self class]);
15     [super drawLayer:layer inContext:ctx];
16 }
17 
18 - (void)drawRect:(CGRect)rect {
19     NSLog(@"%@ - drawRect", [self class]);
20     CGContextRef ctx = UIGraphicsGetCurrentContext();
21     CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
22     CGContextFillEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
23     CGContextFillPath(ctx);
24 }
25 
26 @end
 
layer:(创建一个绿矩形)
 1 //
 2 //  HVWLayer.m
 3 //  CALayerTest
 4 //
 5 //  Created by hellovoidworld on 15/1/14.
 6 //  Copyright (c) 2015年 hellovoidworld. All rights reserved.
 7 //
 8 
 9 #import "HVWLayer.h"
10 
11 @implementation HVWLayer
12 
13 
14 - (void)drawInContext:(CGContextRef)ctx {
15     NSLog(@"%@ - drawInContext", [self class]);
16    
17     // 绿色
18     CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
19     // 矩形
20     CGContextFillRect(ctx, CGRectMake(100, 100, 100, 100));
21     CGContextFillPath(ctx);
22 }
23  
24 @end
 
ViewController:
 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3     // Do any additional setup after loading the view, typically from a nib.
 4    
 5     // 创建view
 6     HVWView *hvwView = [[HVWView alloc] init];
 7     hvwView.backgroundColor = [UIColor grayColor];
 8     hvwView.frame = CGRectMake(50, 50, 250, 250);
 9    
10     // 创建layer
11     HVWLayer *layer = [[HVWLayer alloc] init];
12     layer.frame = CGRectMake(50, 50, 250, 250);
13     layer.backgroundColor = [UIColor blueColor].CGColor;
14    
15     // 必须要重绘才能调用layer的drawInContext
16     [layer setNeedsDisplay];
17    
18     // 配置layer到view,会设置layer的delegate就是那个view
19     [hvwView.layer addSublayer:layer];
20    
21     [self.view addSubview:hvwView];
22 //    [self.view.layer addSublayer:layer];
23 }
 
Image(21)
 
consol:
2015-01-14 21:59:45.558 CALayerTest3[3246:115305] HVWView - drawLayer
2015-01-14 21:59:45.560 CALayerTest3[3246:115305] HVWView - drawRect
2015-01-14 21:59:45.561 CALayerTest3[3246:115305] HVWLayer - drawInContext
 
posted @ 2015-01-16 10:30  HelloVoidWorld  阅读(520)  评论(0编辑  收藏  举报