iOS DeveloperiOS

对CALayer与UIView的一些理解

2016-06-05  本文已影响108人  乌黑的太阳

本人写的只是基于自己的理解,如有不对的地方可以指正勿喷。本篇不是科普介绍,只是一些自己的理解。

CALayer与UIView联系

UIView其实就是在CALayer的基础上增加了触摸处理,你视觉所看到的UIView的基本就是CALayer。因为NSView(OSX)与UIView(IOS)交互处理不同,一个基于鼠标,一个基于触摸,所以才将CALayer单独提取出来复用(CALayer是纯粹用来做显示的)。

CALayer的一些概念

显示相关

一个layer可以理解成一个“色块”,你能看见的一个矩形。你要这个色块上显示文字、图片等东西的话有三种途径:
1、添加带有文字、图片等的子图层(你可以将图层想象成一张纸,这儿相当于将另一张带有文字或者图片的纸贴在一张纸上面);
2、设置contents寄宿图层属性,它接受一个id类型,实际上你必须提供CGImage对象才能显示;
3、直接在图层的寄宿图层上写文字或者画图片,可以新建一个子类重写

- (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一致。(也需要将隐式动画关闭,不然只会执行第二个隐式动画)

上一篇 下一篇

猜你喜欢

热点阅读