CALayer深度解析

来源:互联网 发布:avast扫描慢 知乎 编辑:程序博客网 时间:2024/06/03 09:24
1.什么是CALayer
在iOS系统中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView.
其实UIView之所以能显示在屏幕上,完全是因为它内部的一个层。
在创建UIView对象时,UIView内部会自动创建一个层(即CALayer对象),通过UIView的layer属性可以访问这个层。当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示。
换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能。
2.UIView与CALayer的区别和联系
UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的(Mac下似乎不是这样)。它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame,bounds等等,实际上内部都是在访问它所包含的CALayer的相关属性。
就是说我们在操作UIView的一些跟绘图和坐标有关的属性的时候,比如, self.view.backGround =[UIColor yellowColor] ,本质仍然是对CLayer做了操作. 由于代码封装我们看不到罢了.
UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,例如通
+ (Class)layerClass; 类方法
- (class) layerClass {  
  return ([CAEAGLLayer class]); 
}  
使某个UIView的子类使用GL来进行绘制。
UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表示。例如下面的代码
grayCover = [[CALayer alloc] init];  
grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];  
[self.layer addSubLayer: grayCover];  
会在目标View上敷上一层黑色的透明薄膜
坐标系系统(对position和anchorPoint的关系还是犯晕)
CALayer的坐标系系统和UIView有点不一样,它多了一个叫anchorPoint的属性,它使用CGPoint结构,但是值域是0~1,也就是 按照比例来设置。这个点是各种图形变换的坐标原点,同时会更改layer的position的位置,它的缺省值是{0.5, 0.5},也就是在layer的中央。
某layer.anchorPoint = CGPointMake(0.f, 0.f);
如果这么设置,layer的左上角就会被挪到原来的中间的位置,
加上这样一句就好了
某layer.position = CGPointMake(0.f, 0.f);
3.CALayer的创建和初始化方法:
+ (instancetype)layer;  
- (instancetype)init;  
- (instancetype)initWithLayer:(id)layer; 
- (nullable id)presentationLayer;//是Layer的显示层(呈现层),需要动画提交之后才会有值。  
- (id)modelLayer;//模型层,在呈现图层上调用–modelLayer将会返回它正在呈现所依赖的CALayer。通常在一个图层上调用-modelLayer会返回–self
4.CALayer的一些方法和属性:
//返回这个属性名所对应的属性值的默认值,如果默认值是未知的,则返回nil,子类可以重载这个方法,来设定一些默认值。  
+ (nullable id)defaultValueForKey:(NSString *)key;
// 子类重载方法,当属性改变(也包括通过动画造成的layer的改变)需要重绘layer的内容时,返回YES。这个方法默认返回NO,不要通过CALayer返回YES,这样会出现不定的错误。  
+ (BOOL)needsDisplayForKey:(NSString *)key;  
//在调用-encodeWithCode方法时使用,表示某一属性值是否可以归档。默认YES,可以归档。子类中需要对自定义的属性归档的话,可以调用这个方法  
- (BOOL)shouldArchiveValueForKey:(NSString *)key;
//层的边界,默认为CGRectZero。支持动画。  
@property CGRect bounds;  
//层的界定,用于界定在父层中的位置,默认值零点zero point,支持动画  
@property CGPoint position;
//层在父层上的位置的Z轴的分量,默认值零zero,支持动画  
@property CGFloat zPosition;    
//限定层边界的锚点,就像在归一化的层的点坐标,'(0,0)'是边界矩形的左下角'(1,1)'是右上角。默认为'(0.5,0.5)“,即边界矩形的中心。支持动画。  
@property CGPoint anchorPoint;    
//层的锚点的Z分量(参考点位置和变换),默认为零。支持动画。  
@property CGFloat anchorPointZ;    
//3D变换,用于层边界相对于锚点的变换。默认为恒等变换。支持动画。  
@property CATransform3D transform;
imageView.layer.transform = CATransform3DMakeRotation(M_PI_4001);
//用来访问'变换'属性:仿射变换的存取器方法。  
- (CGAffineTransform)affineTransform;  
- (void)setAffineTransform:(CGAffineTransform)m;  
//与View的frame属性不同,在层次结构中每一层都有一个隐含的帧长方形, `position', `bounds', `anchorPoint',and `transform'属性改变时,它也会发生相应的变化  
@property CGRect frame;    
//当为YES时不显示层与其子层,默认是NO,支持动画  
@property(getter=isHidden) BOOL hidden;  
//当时false时,层远离观察者的那一面隐藏(图层有双面,是否都显示,设置NO意思背面看不到,当为NO时,然后旋转180度,则看不到layer层),默认是YES,支持动画。 
@property(getter=isDoubleSided) BOOL doubleSided;   
//表示层(及其子层)的几何是否被垂直旋转,默认NO。该属性可以改变默认图层y坐标的方向。当翻转变换被调用时,使用该属性来调整图层的方向有的时候是必需的。如果父视图使用了翻转变换,它的子视图内容(以及它对应的图层)将经常被颠倒。在这种情况下,设置子图层的geometryFlipped属性为YES是一种修正该问题最简单的方法。在OS X 10.8及以上版本,AppKit负责管理该属性,你不应该更改它。对于iOS app,不推荐使用geometryFlipped属性。  
//是否进行y轴的方向翻转  
@property(getter=isGeometryFlipped) BOOL geometryFlipped;  
//获取当前layer内容y轴方向是否被翻转了 
- (BOOL)contentsAreFlipped;  
//父层  
@property(nullable, readonlyCALayer *superlayer;    
//从其父layer层上移除  
- (void)removeFromSuperlayer;  
//所有子layer数组  
@property(nullable, copy) NSArray<CALayer *> *sublayers;
//添加一个子layer 
- (void)addSublayer:(CALayer *)layer;   
//插入一个子layer
- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx;
- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling;  
- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling;    
//替换一个子layer  
- (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2;    
//对其子layer进行3D变换  
@property CATransform3D sublayerTransform;    
//遮罩层layer  @property(nullable, strongCALayer *mask;    
//是否进行bounds的切割,在设置圆角属性时会设置为YES  @property BOOL masksToBounds;   
//下面这些方法用于坐标转换  
- (CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;  
- (CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;  
- (CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;  
- (CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;    
//  
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;  
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;      
//===================命中检测方法  
//iOS中,hit-Testing的作用就是找出这个触摸点下面的View(layer)是什么,HitTest会检测这个点击的点是不是发生在这个View(layer)上  
//返回包含某一点的最上层的子layer  
- (nullable CALayer *)hitTest:(CGPoint)p;    
//返回layer是否包含某一点  
- (BOOL)containsPoint:(CGPoint)p;      
//===================layer内容属性和方法  
//设置layer的内容,一般会设置为CGImage的对象  
@property(nullable, strongid contents;    
//获取内容的rect尺寸  
@property CGRect contentsRect;    
/*contentsGravity属性决定了内容对齐与填充方式,它可以分为两个方面:  1.不改变内容的原始大小  这种模式中不会改变内容的原始大小,如果层的尺寸小于内容的尺寸,则内容会被切割,如果层的尺寸大于内容的尺寸,多出的部分将会显示层的背景颜色。  2.改变内容的尺寸大小  这种模式设置的实际上是一种填充方式: */  
@property(copyNSString *contentsGravity;    
//设置内容的缩放  
@property CGFloat contentsScale 
//这个属性确定一个矩形区域,当内容进行拉伸或者缩放的时候,这一部分的区域是会被形变的,例如默认设置为(0,0,1,1),则整个内容区域都会参与形变。如果我们设置为(0.25,0.25,0.5,0.5),那么只有中间0.5*0.5比例宽高的区域会被拉伸,四周都不会。  
@property CGRect contentsCenter;    
//设置缩小的模式  
@property(copyNSString *minificationFilter;    
//设置放大的模式  
@property(copyNSString *magnificationFilter;    
//缩放因子  
@property float minificationFilterBias;    
//设置内容是否完全不透明。默认是NO  
@property(getter=isOpaque) BOOL opaque;    
//重新加载绘制内容  
- (void)display;    
//设置内容为需要重新绘制  
- (void)setNeedsDisplay;  
//设置某一区域内容需要重新绘制  
- (void)setNeedsDisplayInRect:(CGRect)r;    
//获取是否需要重新绘制  
- (BOOL)needsDisplay;    
//如果需要,进行内容重绘  
- (void)displayIfNeeded;    
//这个属性设置为YES,当内容改变时会自动调用- (void)setNeedsDisplay函数.默认是NO  
@property BOOL needsDisplayOnBoundsChange;    
//默认是NO  @property BOOL drawsAsynchronously    
//绘制与读取内容  
- (void)drawInContext:(CGContextRef)cox;  
- (void)renderInContext:(CGContextRef)cox;    
//这个属性值用于限定层的边缘是如何栅格化。通常,该属性用于关闭抗锯齿用于边沿的其他紧靠层的边缘,以消除否则会发生的接缝。默认值时所有值都抗锯齿。  
@property CAEdgeAntialiasingMask edgeAntialiasingMask;    
//当为真时,则层对由edgeAntialiasingMask属性的值要求的边抗锯齿。默认值是从主束的Info.plist布尔UIViewEdgeAntialiasing属性读取。如果Info.plist中没有找到值则,默认值是NO。  
@property BOOL allowsEdgeAntialiasing;    
//设置背景颜色 默认nil.  
@property(nullable) CGColorRef backgroundColor;    
//设置圆角半径 默认zero  
@property CGFloat cornerRadius;    
//设置边框宽度  
@property CGFloat borderWidth;    
//设置边框颜色  
@property(nullable) CGColorRef borderColor;    
//设置透明度  
@property float opacity;    
//(待续。。。)  
@property BOOL allowsGroupOpacity;    
@property(nullable, strongid compositingFilter;    
@property(nullable, copyNSArray *filters;    
@property(nullable, copyNSArray *backgroundFilters;    
@property BOOL shouldRasterize;    
@property CGFloat rasterizationScale; 
//===================layer的阴影属性  
//设置阴影颜色  
@property(nullable) CGColorRef shadowColor;  
//设置阴影透明度,默认0,值在[0,1]之间,支持动画  
@property float shadowOpacity;  
//设置阴影偏移量. 默认(0, -3),支持动画.  
@property CGSize shadowOffset;    
//设置阴影圆角半径  
@property CGFloat shadowRadius;  
//设置阴影路径.默认null,支持动画.  
@property(nullable) CGPathRef shadowPath;  
//===================布局方法  
- (CGSize)preferredFrameSize;  
- (void)setNeedsLayout;  
- (BOOL)needsLayout;  
- (void)layoutIfNeeded;  
- (void)layoutSublayers;  
//===================行为方法  
+ (nullable id<CAAction>)defaultActionForKey:(NSString *)event;  
- (nullable id<CAAction>)actionForKey:(NSString *)event;  
@property(nullable, copy) NSDictionary<NSString *, id<CAAction>> *actions;  
//===================layer的关于动画的方法  
//添加一个动画对象 key值起到id的作用,通过key值,可以取到这个动画对象  
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;  
//移除所有动画对象  
- (void)removeAllAnimations;  
//移除某个动画对象  
- (void)removeAnimationForKey:(NSString *)key;  
//获取所有动画对象的key值  
- (nullable NSArray<NSString *> *)animationKeys;  
//通过key值获取动画对象  
- (nullable CAAnimation *)animationForKey:(NSString *)key;  
//===================layer的其他属性  
//layer的名字,用于层的管理,默认nil  
@property(nullable, copyNSString *name;  
  
//代理,默认nil  
@property(nullable, weak) id delegate;  
//风格属性字典  
@property(nullable, copyNSDictionary *style;  
@end  
//=====================CAAction协议  
@protocol CAAction  
/* 
CAAction协议定义了行为对象如何被调用。实现CAAction协议的类包含一个方法runActionForKey:object:arguments:。当行为对象收到一个 
runActionForKey:object:arguments:的消息时,行为标识符、行为发生所在的图层、额外的参数字典会被作为参数传递给方法。通常行为对象是CAAnimation的 
子类实例,它实现了CAAction协议。然而你也可以返回任何实现了CAAction协议的类对象。当实例收到runActionForKey:object:arguments:的消息时,它需要执 
行相应的行为。 
*/  
- (void)runActionForKey:(NSString *)event object:(id)anObject  
              arguments:(nullable NSDictionary *)dict;  
@end  
// NSNull protocol conformance.  (待续。。。)  
@interface NSNull (CAActionAdditions) <CAAction>  
@end  
//==================NSObject的类别  
//绘制  
@interface NSObject (CALayerDelegate)
- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox;
- (void)layoutSublayersOfLayer:(CALayer *)layer;
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;  
@end  
//  
//********************** Layer的contentsGravity 属性值******************************/  
//1.不改变内容的原始大小.下面的这些设置方式为这种模式:  
CA_EXTERN NSString * const kCAGravityCenter  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAGravityTop  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityBottom  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityLeft
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityRight  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityTopLeft  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityTopRight  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityBottomLeft  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityBottomRight  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
//改变内容的尺寸大小.这种模式设置的实际上是一种填充方式参数如下:  
CA_EXTERN NSString * const kCAGravityResize  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0); 
CA_EXTERN NSString * const kCAGravityResizeAspect  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAGravityResizeAspectFill  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
//********************** Layer的Contents filter names.模式参数如下**/  
//临近插值  
CA_EXTERN NSString * const kCAFilterNearest  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
//线性拉伸  
CA_EXTERN NSString * const kCAFilterLinear  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
//瓦片复制拉伸  
CA_EXTERN NSString * const kCAFilterTrilinear  
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_3_0);  
/** Layer event names. **/  
CA_EXTERN NSString * const kCAOnOrderIn  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
CA_EXTERN NSString * const kCAOnOrderOut  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
/** The animation key used for transitions. **/  
CA_EXTERN NSString * const kCATransition  
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);  
NS_ASSUME_NONNULL_END  

注意:为什么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
UIView和CALayer的选择
其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以
当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级
UIView和CALayer的其他关系
UIView可以通过subviews属性访问所有的子视图,类似地,CALayer也可以通过sublayers属性访问所有的子层
UIView可以通过superview属性访问父视图,类似地,CALayer也可以通过superlayer属性访问父层
position和anchorPoint这2个属性的理解
anchorPoint默认是(0.5, 0.5)
myLayer.position = CGPointMake(100100);  相当于layer中心点在(100,100)







我想通过上述的四张图片应该能理解了这两个属性的用法了
anchorPoint决定着CALayer身上的哪个点会在position所指定的位置上。它的x、y取值范围都是0~1,默认值为(0.5, 0.5),因此,默认情况下,CALayer的中点会在position所指定的位置上

5.自定义图层
创建一个CALayer的子类,然后覆盖drawInContext:方法,使用Quartz2D API进行绘图如图:自定义YQCustomeLayer继承与CALayer的一个子类 实现drawInContext这个方法

   
    YQCustomeLayer *layer = [YQCustomeLayerlayer];
    //设置层的宽高
    layer.
bounds =CGRectMake(0,0,100,100);
   
//设置层的位置
    layer.
position =CGPointMake(100,100);
   
//开始绘制图层
    [layer
setNeedsDisplay];//需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图
    [
self.view.layeraddSublayer:layer];

 //第二种:在controller中显示
   
CALayer *layer1 = [CALayerlayer];
   
//设置delegate
    layer1.
delegate =self;//这里设置成代理来实现drawLayer:inContext:这个方法
   
//设置层的宽高
    layer1.
bounds =CGRectMake(0,0,100,100);
   
//设置层的位置
    layer1.
position =CGPointMake(100,100);
   
//开始绘制图层
    [layer1
setNeedsDisplay];//但是也要写这个方法否则不走- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx这个放法
    [
self.view.layeraddSublayer:layer1];

#pragma mark画一个矩形框
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
   
//设置蓝色
   
CGContextSetRGBStrokeColor(ctx,0,0,1,1);
   
//设置边框宽度
   
CGContextSetLineWidth(ctx,10);
   
//添加一个跟层一样大的矩形到路径中
   
CGContextAddRect(ctx, layer.bounds);
   
//绘制路径
   
CGContextStrokePath(ctx);
}

注意:
无论采取哪种方法来自定义层,都必须调用CALayer的setNeedsDisplay方法才能正常绘图。
UIView的详细显示过程
当UIView需要显示时,它内部的层会准备好一个CGContextRef(图形上下文),然后调用delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象。而UIView在drawLayer:inContext:方法中又会调用自己的drawRect:方法
平时在drawRect:中通过UIGraphicsGetCurrentContext()获取的就是由层传入的CGContextRef对象,在drawRect:中完成的所有绘图都会填入层的CGContextRe
中,然后被拷贝至屏幕

gitHub地址:https://github.com/yaoqiGetHub/CALayer


0 0