IOS进阶 - CALayer简单介绍

第一部分:简介

一、什么是CALayer

     * 在iOS系统中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView。

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

     * 在创建UIView对象时,UIView内部会自动创建一个层(即CALayer对象),通过UIView的layer属性可以访问这个层。当UIView需要显示到屏幕上时,会调用                  drawRect:方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示。

     * 换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能。

二、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 {
  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上敷上一层黑色的透明薄膜。
     * UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。
       第一份,逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
       第二份,动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
       第三份,显示树,这棵树的内容是当前正被显示在屏幕上的内容。
       这三棵树的逻辑结构都是一样的,区别只有各自的属性。

     *动画的运作
      UIView的主layer以外(我觉得是这样),对它的subLayer,也就是子layer的属性进行更改,系统将自动进行动画生成,动画持续时间有 个缺省时间,个人感觉大概是0.5秒。在动画时间里,系统自动判定哪些属性更改了,自动对更改的属性进行动画插值,生成中间帧然后连续显示产生动画效果。

     *坐标系系统(对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);

     *layer可以设置圆角显示,例如UIButton的效果,也可以设置阴影显示,但是如果layer树中的某个layer设置了圆角,树中所有layer 的阴影效果都将显示不了了。如果既想有圆角又想要阴影,好像只能做两个重叠的UIView,一个的layer显示圆角,一个的layer显示阴 影.....
上面已经说过了,UIView之所以能够显示,完全是因为内部的CALayer对象。因此,通过操作这个CALayer对象,可以很方便地调整UIView的一些界面属性,比如:阴影、圆角大小、边框宽度和颜色等。

三,CALayer的头文件

//CAEdgeAntialiasingMask的值
typedef NS_OPTIONS (unsigned int, CAEdgeAntialiasingMask){
    kCALayerLeftEdge      = 1U << 0,      /* Minimum X edge. */
    kCALayerRightEdge     = 1U << 1,      /* Maximum X edge. */
    kCALayerBottomEdge    = 1U << 2,      /* Minimum Y edge. */
    kCALayerTopEdge       = 1U << 3,      /* Maximum Y edge. */
};

//==================Layer的创建和初始化
+ (instancetype)layer;
- (instancetype)init;
- (instancetype)initWithLayer:(id)layer; //(待续。。。)
- (nullable id)presentationLayer;//是Layer的显示层(呈现层),需要动画提交之后才会有值。
- (id)modelLayer;//模型层,在呈现图层上调用–modelLayer将会返回它正在呈现所依赖的CALayer。通常在一个图层上调用-modelLayer会返回–self
// 呈现层和模型层见http://www.360doc.com/showWeb/0/0/543080743.aspx#
 
//===================属性方法
/*
 CALayer的类或者其子类的所有ObjectiveC属性都遵循了NSKeyValueCoding协议,对于子类声明的属性,它会动态的实现缺省的存取器方法。当使用KVC时,它的属
 性不是一个对象类型,这时我们需要进行类型的转换,这里列举了NSValue类的扩展,它支持下面的类型的转换。基础类型可以用NSNumber进行转
*/
// *      C Type                  Class
// *      ------                  -----
// *      CGPoint                 NSValue
// *      CGSize                  NSValue
// *      CGRect                  NSValue
// *      CGAffineTransform       NSAffineTransform
// *      CATransform3D           NSValue  */

//返回这个属性名所对应的属性值的默认值,如果默认值是未知的,则返回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;

//用来访问'变换'属性:仿射变换的存取器方法。
- (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)ctx;
- (void)renderInContext:(CGContextRef)ctx;

//这个属性值用于限定层的边缘是如何栅格化。通常,该属性用于关闭抗锯齿用于边沿的其他紧靠层的边缘,以消除否则会发生的接缝。默认值时所有值都抗锯齿。
@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)ctx;

- (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

@end

四、CALayer的简单使用

1.CALayer是被定义在QuartzCore框架中的,因此要想使用CALayer,先导入QuartzCore框架(如果已经导入了UIKit框架,就无需导入QuartzCore框架,因为UIKit本身已经引入QuartzCore框架 )
1> 点击项目名称,然后点击右边TARGETS下面的target



2> 点击Build Pases后,展开Link Binary....,添加 + 号

3> 在搜索框中输入"Quartz",选中QuartzCore.framework,最后add添加

4> 添加完毕后,这个框架就会出现在项目文件夹中

如果你觉得位置不好看,还可以将它拖到Frameworks文件夹下,跟其他框架放一起

2.在项目代码中导入QuartzCore框架的主头文件
   #import <QuartzCore/QuartzCore.h>
3.通过CALayer修改UIImageView的界面属性
你也可以使用UIButton或者UILabel,这里就以UIImageView为例子
1> 先创建一个UIImageView,添加到控制器的view中   
   UIImage *image = [UIImage imageNamed:@"lufy.png"];
   UIImageView *imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
   imageView.center = CGPointMake(100, 100);
   [self.view addSubview:imageView];


2> 设置阴影
  imageView.layer.shadowColor = [UIColor grayColor].CGColor;
  imageView.layer.shadowOffset = CGSizeMake(10, 10);
  imageView.layer.shadowOpacity = 0.5;<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"
* 第1行设置阴影的颜色为灰色,注意,这里使用的是UIColor的CGColor属性,是一种CGColorRef类型的数据

* 第2行设置阴影的偏移大小,可以看出阴影往原图的右下角偏移

* 第3行设置阴影的不透明度为0.5,表示半透明。如果为1,代表完全不透明。


3> 设置圆角大小

通过layer属性可以访问视图内部的CALayer对象

   imageView.layer.cornerRadius = 10;
   imageView.layer.masksToBounds = YES;

* 第1行设置圆角半径为10

* 第2行的maskToBounds=YES:可以看做是强制内部的所有子层支持圆角效果,少了这个设置,UIImageView是不会有圆角效果的

* 注意,如果设置了maskToBounds=YES,那将不会有阴影效果


4> 设置边框宽度和颜色
   imageView.layer.borderWidth = 5;
   imageView.layer.borderColor = [UIColor redColor].CGColor;
* 第1行设置边框宽度为5

* 第2行设置边框颜色为红色


5> 设置旋转
imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
* 利用transform属性可以设置旋转、缩放等效果
* M_PI_4表示四分之π,顺时针旋转45°
* 后面的(0, 0, 1)表示Z轴这个向量,修改这个向量可以做一些三维旋转效果,你可以随便改个值试一下,比如(1, 1, 1)

* 总体的意思是layer会绕着Z轴顺时针旋转45°,也就是在x、y平面进行旋转


第二部分:创建新的层

一、添加一个简单的图层

  CALayer *myLayer = [CALayer layer];
  //设置层的宽度和高度(100x100)
  myLayer.bounds = CGRectMake(0, 0, 100, 100);
  //设置层的位置
  myLayer.position = CGPointMake(100, 100);
  //设置层的背景颜色:红色
  myLayer.backgroundColor = [UIColor redColor].CGColor;
  //设置层的圆角半径为10
  myLayer.cornerRadius = 10;
  //添加myLayer到控制器的view的layer中
 [self.view.layer addSublayer:myLayer];

* 第1行创建了一个自动释放的CALayer对象,你也可以使用经典的alloc和init方法来创建
* 第12行将创建好的层添加到控制器的view的层中


二、添加一个显示图片的图层

   CALayer *myLayer = [CALayer layer];
  // 设置层的宽度和高度(100x100)
   myLayer.bounds = CGRectMake(0, 0, 100, 100);
  // 设置层的位置
   myLayer.position = CGPointMake(100, 100);
  // 设置需要显示的图片
   myLayer.contents = (id)[UIImage imageNamed:@"lufy.png"].CGImage;
  // 设置层的圆角半径为10
   myLayer.cornerRadius = 10;
  // 如果设置了图片,需要设置这个属性为YES才有圆角效果
   myLayer.masksToBounds = YES; 
  // 添加myLayer到控制器的view的layer中
   [self.view.layer addSublayer:myLayer];
* 在第7行设置需要显示的图片,注意,这里用的是UIImage的CGImage属性,是一种CGImageRef类型的数据

三、为什么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的选择

细心的朋友不难发现,其实前面的2个效果不仅可以通过添加层来实现,还可以通过添加UIView来实现。比如,第1个红色的层可以用一个UIView来实现,第2个显示图片的层可以用一个UIImageView来实现。 既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?

* 其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
* 所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以
* 当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级

五、UIView和CALayer的其他关系

* UIView可以通过subviews属性访问所有的子视图,类似地,CALayer也可以通过sublayers属性访问所有的子层
* UIView可以通过superview属性访问父视图,类似地,CALayer也可以通过superlayer属性访问父层
* 下面再看一张UIView和CALayer的关系图:


如果两个UIView是父子关系,那么它们内部的CALayer也是父子关系。

第三部分层的属性

一、隐式动画属性

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

* 与UIView不同,CALayer实际上包含了一个表现层和一个模型层。模型层是用来在内存中存储必要的图层信息的。表现层则是用在将图层显示在屏幕上,并为此做了相应的优化。

如果说一个动画是隐式动画,那就意味着用做动画的属性是在模型层中被修改的然后在通过表现层传递出来,并最终显示在屏幕上。 如果动画是显示动画,进行动画的属性就是只存在于表现层的,而原始的模型层(在进行动画之前的)会保持不变。这就意味着除非做其他的动作,否则在一个显示动画结束之后,CALayer会回到动画开始之前的状态,因为下面的模型层并没有被修改.

* 当对非Root Layer的部分属性进行相应的修改时,默认会自动产生一些动画效果,这些属性称为Animatable Properties(可动画属性)。
* 列举几个常见的Animatable Properties:

bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
position:用于设置CALayer的位置。修改这个属性会产生平移动画
比如:假设一开始CALayer的position为(100, 100),然后在某个时刻修改为(200, 200),那么整个CALayer就会在短时间内从(100, 100)这个位置平移到(200, 200)
* 我们也可以从官方文档中查询所有的Animatable Properties

1.点击Window -> Organizer

2.在搜索框输入"animatable"即可

二、position和anchorPoint

* position和anchorPoint属性都是CGPoint类型的

* position可以用来设置CALayer在父层中的位置,它是以父层的左上角为坐标原点(0, 0)

* anchorPoint称为"定位点",它决定着CALayer身上的哪个点会在position属性所指的位置。它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
1.创建一个CALayer,添加到控制器的view的layer中

  CALayer *myLayer = [CALayer layer];
   // 设置层的宽度和高度(100x100)
  myLayer.bounds = CGRectMake(0, 0, 100, 100);
  // 设置层的位置
  myLayer.position = CGPointMake(100, 100);
  // 设置层的背景颜色:红色
  myLayer.backgroundColor = [UIColor redColor].CGColor; 
  // 添加myLayer到控制器的view的layer中
 [self.view.layer addSublayer:myLayer];
第5行设置了myLayer的position为(100, 100),又因为anchorPoint默认是(0.5, 0.5),所以最后的效果是:myLayer的中点会在父层的(100, 100)位置

注意,蓝色线是我自己加上去的,方便大家理解,并不是默认的显示效果。两条蓝色线的宽度均为100。
2.若将anchorPoint改为(0, 0),myLayer的左上角会在(100, 100)位置

  myLayer.anchorPoint = CGPointMake(0, 0);

3.若将anchorPoint改为(1, 1),myLayer的右下角会在(100, 100)位置
 myLayer.anchorPoint = CGPointMake(1, 1);
 
4.将anchorPoint改为(0, 1),myLayer的左下角会在(100, 100)位置
 myLayer.anchorPoint = CGPointMake(0, 1);


我想,你应该已经明白anchorPoint的用途了吧,它决定着CALayer身上的哪个点会在position所指定的位置上。它的x、y取值范围都是0~1,默认值为(0.5, 0.5),因此,默认情况下,CALayer的中点会在position所指定的位置上。当anchorPoint为其他值时,以此类推。

第四部分:自定义图层

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



2.在.m文件中覆盖drawInContext:方法,在里面绘图
 @implementation MJLayer  
  #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

3.在控制器中添加图层到屏幕上

  MJLayer *layer = [MJLayer layer];
   //设置层的宽高
  layer.bounds = CGRectMake(0, 0, 100, 100);
   //设置层的位置
  layer.position = CGPointMake(100, 100);
   // 开始绘制图层
  [layer setNeedsDisplay];
 [self.view.layer addSublayer:layer];

注意第7行,需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图

二、自定义层的方法2

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


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

  CALayer *layer = [CALayer layer];
  // 设置delegate
   layer.delegate = self;
  // 设置层的宽高
   layer.bounds = CGRectMake(0, 0, 100, 100);
  // 设置层的位置
   layer.position = CGPointMake(100, 100);
  // 开始绘制图层
   [layer setNeedsDisplay];
   [self.view.layer addSublayer:layer];

* 在第3行设置了CALayer的delegate,这里的self是指控制器
* 注意第9行,需要调用setNeedsDisplay这个方法,才会通知delegate进行绘图
2.让CALayer的delegate(前面设置的是控制器)实现drawLayer:inContext:方法

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

 三、其他

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

参考:

CALayer的简单介绍

CALayer的使用

更多动画知识:

iOS Core Animation: Advanced Techniques中文译本


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值