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
CATransform
3
D transform;
imageView
.layer
.transform
= CATransform
3
DMakeRotation(M_PI_
4
,
0
,
0
,
1
);
//用来访问'变换'属性:仿射变换的存取器方法。
- (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,
readonly
)
CALayer
*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, strong) CALayer *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, strong) id contents;
//获取内容的rect尺寸
@property CGRect contentsRect;
/*contentsGravity属性决定了内容对齐与填充方式,它可以分为两个方面:
1.不改变内容的原始大小
这种模式中不会改变内容的原始大小,如果层的尺寸小于内容的尺寸,则内容会被切割,如果层的尺寸大于内容的尺寸,多出的部分将会显示层的背景颜色。
2.改变内容的尺寸大小
这种模式设置的实际上是一种填充方式:
*/
@property(copy) NSString *contentsGravity;
//设置内容的缩放
@property CGFloat contentsScale
//这个属性确定一个矩形区域,当内容进行拉伸或者缩放的时候,这一部分的区域是会被形变的,例如默认设置为(0,0,1,1),则整个内容区域都会参与形变。如果我们设置为(0.25,0.25,0.5,0.5),那么只有中间0.5*0.5比例宽高的区域会被拉伸,四周都不会。
@property CGRect contentsCenter;
//设置缩小的模式
@property(copy) NSString *minificationFilter;
//设置放大的模式
@property(copy) NSString *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, strong) id compositingFilter;
@property(nullable, copy) NSArray *filters;
@property(nullable, copy) NSArray *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,
copy
)
NSString
*name;
//代理,默认nil
@property
(nullable, weak)
id
delegate;
//风格属性字典
@property
(nullable,
copy
)
NSDictionary
*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_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityTop
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityBottom
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityLeft
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityRight
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityTopLeft
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityTopRight
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityBottomLeft
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityBottomRight
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
//改变内容的尺寸大小.这种模式设置的实际上是一种填充方式参数如下:
CA_EXTERN
NSString
*
const
kCAGravityResize
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityResizeAspect
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAGravityResizeAspectFill
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
//********************** Layer的Contents filter names.模式参数如下**/
//临近插值
CA_EXTERN
NSString
*
const
kCAFilterNearest
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
//线性拉伸
CA_EXTERN
NSString
*
const
kCAFilterLinear
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
//瓦片复制拉伸
CA_EXTERN
NSString
*
const
kCAFilterTrilinear
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
6
, __IPHONE_
3
_
0
);
/** Layer event names. **/
CA_EXTERN
NSString
*
const
kCAOnOrderIn
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
CA_EXTERN
NSString
*
const
kCAOnOrderOut
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
5
, __IPHONE_
2
_
0
);
/** The animation key used for transitions. **/
CA_EXTERN
NSString
*
const
kCATransition
__OSX_AVAILABLE_STARTING (__MAC_
1
0
_
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(100, 100);
相当于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 = [
YQCustomeLayer
layer
];
// 设置层的宽高
layer. bounds = CGRectMake ( 0 , 0 , 100 , 100 );
// 设置层的位置
layer. position = CGPointMake ( 100 , 100 );
// 开始绘制图层
[layer setNeedsDisplay ]; // 需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图
[ self . view . layer addSublayer :layer];
// 设置层的宽高
layer. bounds = CGRectMake ( 0 , 0 , 100 , 100 );
// 设置层的位置
layer. position = CGPointMake ( 100 , 100 );
// 开始绘制图层
[layer setNeedsDisplay ]; // 需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图
[ self . view . layer addSublayer :layer];
//
第二种:在
controller
中显示
CALayer *layer1 = [ CALayer layer ];
// 设置 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 . layer addSublayer :layer1];
CALayer *layer1 = [ CALayer layer ];
// 设置 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 . layer addSublayer :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);
}
- ( 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