CALayer 属性及方法介绍

如下图:UIView可以处理触摸事件,但CALayer是不支持交互的(不清楚具体的响应链的)。实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。

<四个层级关系>:视图层级、图层树、呈现树和渲染树。

Calayer属性

  • Contents

事实上,你真正要赋值的类型应该是CGImageRef,它是一个指向CGImage结构的指针。UIImage有一个CGImage属性,它返回一个”CGImageRef”,如果你想把这个值直接赋值给CALayer的contents,那你将会得到一个编译错误。因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型。可以通过bridged关键字转换。

1
@property(nullable, strong) id contents;
  • contentMode
1
2
//对应UIView的contentMode.  
@property(copy) NSString *contentsGravity;
  • geometryFlipped
1
2
//决定了一个图层的坐标是否相对于父图层垂直翻转。是为了适配iOS和OS X两种不同坐标系的情况。
@property(getter=isGeometryFlipped) BOOL geometryFlipped;
  • zPosition
1
2
//常用于做CATransform3D变换及更改图层的显示顺序。<不能改变事件传递的顺序>
@property CGFloat zPosition;
  • anchorPointZ
1
2
//在Z轴上描述图层位置的浮点类型
@property CGFloat anchorPointZ;
  • position

指定了anchorPoint相对于父图层的位置。

1
2
//对应UIView的center.
@property CGPoint position;
  • anchorPoint

锚点,可以理解为固定图层的点。相对于自身坐标系,取值范围0~1,默认(0.5,0.5)。

1
@property CGPoint anchorPoint;

position和anchorPoint的换算公式:彻底理解position和anchorPoint

1
2
position.x = frame.origin.x + anchorPoint.x * bounds.size.width;  
position.y = frame.origin.y + anchorPoint.y * bounds.size.height;
  • contentsScale

属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。当设置了contentsGravity属性会有所影响。

1
2
//如果contentsScale设置为1.0,将会以每个点1个像素绘制图片。并且把contentsGravity设置为kCAGravityCenter(这个值并不会拉伸图片),那将会有很明显的变化。
@property CGFloat contentsScale
  • contentsRect

属性允许我们在图层边框里显示寄宿图的一个子域,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)

1
@property CGRect contentsRect;

其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。

1
2
//单位坐标,定义的区域会被全面拉伸(也就是从四个方向进行放大或者缩小),所'侵占'的地方的视图也会进行相应的拉伸变换。
@property CGRect contentsCenter;
  • masksToBounds
1
2
//等同于UIView的clipsToBounds.
@property BOOL masksToBounds;
  • mask

是个CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域。

1
@property(nullable, strong) CALayer *mask;
  • minificationFilter && magnificationFilter
1
2
@property(copy) NSString *minificationFilter;//缩小:
@property(copy) NSString *magnificationFilter;//放大:

–值的介绍:

1。kCAFilterLinear:默认值,采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。

2。kCAFilterTrilinear:三线性滤波算法存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而得到最后的结果。这不仅提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题.

3。kCAFilterNearest:取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。(不推荐)。

–总结:

对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很多斜线或是曲线轮廓的图片来说,最近过滤算法会导致更差的结果。换句话说,线性过滤保留了形状,最近过滤则保留了像素的差异。

  • shouldRasterize

为了启用shouldRasterize属性,我们设置了图层的rasterizationScale属性。默认情况下,所有图层拉伸都是1.0, 所以如果你使用了shouldRasterize属性,你就要确保你设置了rasterizationScale属性去匹配屏幕,以防止出现Retina屏幕像素化的问题。(info.plist文件中有个全局设置属性:UIViewGroupOpacity)

1
2
//组透明效果
@property BOOL shouldRasterize;
  • affineTransform

这里只提供了set和get方法。

1
2
- (CGAffineTransform)affineTransform;
- (void)setAffineTransform:(CGAffineTransform)m;

—栗子:

1
2
CGAffineTransform  affine = CGAffineTransformMakeRotation(M_PI_4);
blueLayer.affineTransform = affine;

CALayer代理

CALayer有一个可选的delegate属性,实现了CALayerDelegate协议,当CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的。(delegate属性被声明为id类型,所有的代理方法都是可选的)。

  • 当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示。它通过调用下面这个方法做到的:
1
- (void)displayLayer:(CALayerCALayer *)layer;
  • 如果代理不实现-displayLayer:方法,CALayer就会转而尝试调用下面这个方法,在调用这个方法之前,CALayer创建了一个合适尺寸的空寄宿图(尺寸由boundscontentsScale决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入。
1
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

—栗子:

1
2
3
4
5
6
7
CALayer * delegateLayer = [CALayer layer];
[self.view.layer addSublayer:delegateLayer];
delegateLayer.frame = CGRectMake(50, 100, 300, 400);
delegateLayer.backgroundColor = [UIColor brownColor].CGColor;
delegateLayer.delegate = self;//这里需遵循CALayerDelegate协议。

[delegateLayer display];//这里需要显式地调用了-display。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。
1
2
3
4
5
6
7
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}

—效果:

95187B0D-764B-46CA-B26E-C4E835BD8D03.jpeg

—注意:

尽管我们没有用masksToBounds属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。

当UIView创建了它的宿主图层时,它就会自动地把图层的delegate设置为它自己,并提供了一个-displayLayer:的实现,那所有的问题就都没了。

当使用寄宿了视图的图层的时候,你也不必实现-displayLayer:-drawLayer:inContext:方法来绘制你的寄宿图。通常做法是实现UIView的-drawRect:方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用-display方法。

CALayer方法

  • UIView会在初始化的时候调用+layerClass方法,然后用它的返回类型来创建宿主图层。(特别适用,返回的值代表self.layer)
1
+(Class)layerClass
  • 以下方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形.
1
2
3
4
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;//得到layer上的point的点相对于方法调用者的相对point。
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
  • 接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。需要把触摸坐标转换成每个图层坐标系下的坐标,结果很不方便。
1
- (BOOL)containsPoint:(CGPoint)p;
  • 方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用-containsPoint:那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。
1
- (nullable CALayer *)hitTest:(CGPoint)p;

—栗子:

1
2
3
4
5
6
7
8
9
10
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint point = [[touches anyObject] locationInView:self.view];
CALayer *layer = [self.layerView.layer hitTest:point];
if (layer == self.blueLayer) {

} else if (layer == self.layerView.layer) {

}
}