对CALayer与UIView的一些理解
本人写的只是基于自己的理解,如有不对的地方可以指正勿喷。本篇不是科普介绍,只是一些自己的理解。
CALayer与UIView联系
UIView其实就是在CALayer的基础上增加了触摸处理,你视觉所看到的UIView的基本就是CALayer。因为NSView(OSX)与UIView(IOS)交互处理不同,一个基于鼠标,一个基于触摸,所以才将CALayer单独提取出来复用(CALayer是纯粹用来做显示的)。
CALayer的一些概念
显示相关
一个layer可以理解成一个“色块”,你能看见的一个矩形。你要这个色块上显示文字、图片等东西的话有三种途径:
1、添加带有文字、图片等的子图层(你可以将图层想象成一张纸,这儿相当于将另一张带有文字或者图片的纸贴在一张纸上面);
2、设置contents寄宿图层属性,它接受一个id类型,实际上你必须提供CGImage对象才能显示;
3、直接在图层的寄宿图层上写文字或者画图片,可以新建一个子类重写
- (void)drawInContext:方法,或者实现非正式协议CALayerDelegate的
- (void)dispalyLayer: context:方法,要显示这些画出来的东西必须调用图层的display方法。这个方法的伪代码实现如下:
- (void)display {
if (self.delegate respondToSelector: @selector(displayLayer:)) {
[self.delegate displayLayer: self];
} else {
[self dawInContext:];//(这个方法默认实现里面掉用了[self.delegate displayLayer: context:])
}
[self layoutSublayers];//(这个方法默认实现里面调用了[self.delegate layoutSublayersOfLayer:])
}
重写UIView的drawRect:或者CALayer的drawInContext:或者实现了非正式协议CALayerDelegate的displayLayer: context:系统会为当前图层的寄宿图层提供一个大小为widthheight4byte的上下文ctx,width和height都是像素点大小。这是非常耗费内存的方法,一般没有特殊需求不要重写。(为什么是像素点*4byte,因为一个像素点需要包含red、green、blue,alpha四个值,每个值占一字节)
隐式动画相关
CALayer支持隐式动画的属性其实都是默认用CABasicAnimation制作的,默认已经实现了。调用过程如下:
当你设置一个支持隐式动画的属性时会调用- (CAAction *)actionForKey: 这个方法的实现伪代码如下
- (CAAction *)actionForkey: {
if ([self.delegate respondToSelector @selector(actionForLayer: forKey:)] ) {
return [self.delegate actionForLayer: forKey];
} else {
到actions字典(默认nil) 查找(这个字典可以自己设置),找到了value,则return
否则到style字典查找(这个字典我还没弄懂,默认空,手动设置也无效)
都没找到的话调用-defaultActionForKey: 返回默认实现
}
}
UIView的属性设置时没有默认的动画,是因为通过代理实现了- (CAAction *)actionForLayer: forKey: 返回nil,这样就没有动画了。
显示动画相关
一切图层的显示动画都是unreal, 变化的是presentationLayer(在屏幕上显示后才会存在),图层本身并没有变化,隐式动画进行中的状态也可以通过这个图层查看。
[重新修改一下,这儿应该这么理解,图层其实分为两部分,modelLayer和presentationLayer,modelLayer就是你创建的得到的那个,你可以理解为储存数据的model;在屏幕上显示后会创建一个相应的presentationLayer,这个layer是视觉显示的layer,特别是你做显示动画的时候,modelLayer其实未改变,改变的仅仅只是presentationLayer]
一个无意中的发现:在将animation添加到layer时,animation的keyPath先起作用,如果没有设置就会使用addAnimation:forKey:的key 作为keyPath。
动画添加到图层的时候是深复制了一份添加的,与animationDelegate 协议回调传入的那个动画参数值并不是同一个东西,添加到layer后怎么设置都不会起作用的。
animation的kvc与NSObject不一样,遇到不存在的key时会采用字典类似的方法存储,还可以像字典一样取出。
class UIView: UIResponder {
private func instanceImplementsDrawRect -> Bool {
return UIView.instanceMethodForSelector:(#selector(drawRect:) != instanceMethodForSelector(#selector(drawRect:)
}
func respondsTo(selector: Selector) -> Bool {
// For notes about why this is done, see displayLayer: above.
if (aSelector == @selector(displayLayer:)) {
return !_implementsDrawRect;
} else {
return [super respondsToSelector:aSelector];
}
}
func displayLayer(_ layer: CALayer) {
}
func draw(layer: CALayer, in context: CGContext) {
const CGRect bounds = CGContextGetClipBoundingBox(ctx);
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
if (_clearsContextBeforeDrawing) {
CGContextClearRect(ctx, bounds);
}
if (_backgroundColor) {
[_backgroundColor setFill];
CGContextFillRect(ctx,bounds);
}
CGContextSetShouldSmoothFonts(ctx, NO);
CGContextSetShouldSubpixelPositionFonts(ctx, YES);
CGContextSetShouldSubpixelQuantizeFonts(ctx, YES);
[[UIColor blackColor] set];
[self drawRect:bounds];
CGContextRestoreGState(ctx);
UIGraphicsPopContext();
}
func action(forLayer layer: CALayer, forKey kay: String) -> CAAction? {
if (_animationsEnabled && [_animationGroups lastObject] && theLayer == _layer) {
return [[_animationGroups lastObject] actionForView:self forKey:event] ?: (id)[NSNull null];
} else {
return [NSNull null];
}
}
}
最后多说一句几个概念相关的东西(oc)
分类:会覆盖类中的同名方法,不可添加属性。
扩展:可添加私有属性、方法,必须放在实现区(.m)。
协议:多个类可以共享的一组方法名。协议可以扩展,也可以理解成一个协议可以“继承”另外一个协议。
非正式协议: 一组未实现的分类方法,一般定义在根类,如果要遵循,子类必须重新实现。比较典型的是CALayerDelegate非正式协议
在做layer的动画的时候如果你要保持model和presentation一致,有两个方法可以解决
1、使用代理,在动画完成后将model的值设置为与presentation一致(代理里面记得用CATransaction将隐式动画关闭)
2、在添加动画的代码后直接将model的值设置与presentation一致。(也需要将隐式动画关闭,不然只会执行第二个隐式动画)