美文网首页
Core Animation 一 : CALayer

Core Animation 一 : CALayer

作者: Trigger_o | 来源:发表于2020-11-20 16:34 被阅读0次

    基本概念

    core animation听起来像是一个负责实现动画效果的框架,实际上并非这样,整个UIKit都建立在core animation之上,它的核心是CALayer对象,layer把自身管理的图形内容生成位图从而渲染出来,这也是为什么CALayer会包含在core animation中的原因.


    core animation
    CALayer在core animation框架中

    CALayer可以对接Core Graphics和Core Animation 或者Image I/O, core Image等框架的一些C函数,但它本身是OC对象,所提供的方法也是OC方法,除了CALayer还有一些子类CATextLayer,CAShapeLayer,CAGradientLayer,CAMetalLayer,CAOpenGLLayer等.

    layer定义了一个显示单元的一些属性,如大小(bounds),位置(position),锚点(anchorPoint),以及视觉效果的边缘(border),透明度(opacity),圆角(Radius),阴影(shadow),遮罩(mask),以及变换(transform);
    frame是一个依据大小位置锚点和变换计算出来的值,不是基本属性.

    1.创建一个layer

        UIView *v = [[UIView alloc]initWithFrame:CGRectMake(ScreenWidth/4, ScreenHeight/4, ScreenWidth/2, ScreenHeight/2)];
        v.backgroundColor = [UIColor blackColor];
        [self.view addSubview:v];
        
        CALayer *layer = [CALayer layer];
        layer.bounds = CGRectMake(0, 0, 100, 100);
        layer.anchorPoint = CGPointMake(1, 1);
        layer.backgroundColor = UIColor.lightGrayColor.CGColor;
        [v.layer addSublayer:layer];
    

    2.position和anchorPoint
    在使用UIView的frame时,frame有origin,是个CGPoint,UIView通过origin来定位它处在什么位置;layer也有frame,并且和UIView没有区别;
    layer的position和anchorPoint也可以定位位置;anchorPoint定义一个锚点,可以想象这样一个例子,layer是一张卡片,anchorPoint是一个大头针,position是墙上的位置;
    anchorPoint虽然是CGPoint类型但并非真正的点,数值是从(0,0)到(1,1),默认是(0.5,0.5);(0,0)指左上角,(1,1)指右下角,那么(0.5,0.5)指的就是中心;position定义这个锚点在superLayer的位置,是真正的CGPoint,在上面的例子中,anchorPoint是(1,1),是右下角,position是(0,0),所以最终右下角处在(0,0)的位置.如下图.

    anchorPoint是(1,1)

    3.contents
    CALayer除了背景色,还能显示图片

    layer的结构
    上面是layer结构的示意图,
    contents虽然是id类型,给一个CGImageRef(需要转换成id类型),就可以显示出图片,iOS中有些看起来奇怪的类型,或者方法,或者坐标系等都是为了兼容,或者说为了两方面考虑,也就是mac os方面的使用.
    contentsRect是一个CGRect结构,数值都是0到1,表示取图片的什么范围显示在layer上,例如(0, 0, .5, .5)就是从左上角开始宽高都取1/2来显示;如果设置(.5,.5,1,1)这样超出范围情况,范围外的点会复制边缘的内容
    contentsScale是显示比例,和[UIScreen mainScreen].scale是一个道理,也就是1x,2x,3x的区别,UIImageView是处理好的,CALayer需要手动设置.
    layer.contents = (id)[UIImage imageNamed:@"avatar"].CGImage;
    layer.contentsRect = CGRectMake(0, 0, .5, .5);
    layer.contentsScale = [UIScreen mainScreen].scale;
    
    contentsRect
    (.5, .5, 1, 1)

    contentsCenter是一个CGRect,功能是取一个矩形范围,这个范围可以进行拉伸,而范围之外的部分不被拉伸,和UIImage的resizableImageWithCapinsets基本一样;
    1.contentsCenter的每个参数取值是0到1
    2.需要注意和显示模式contentsGravity结合,kCAGravityBottom之类的不会起效

    4.显示模式

    layer.contentsGravity = kCAGravityBottom;
    

    UIView可以使用contentMode来设置内容在视图内的范围定位和缩放,其实是封装的layer层的contentsGravity属性;
    默认是kCAGravityResize,缩放填充;需要注意的是,这个属性的top和bottom在iOS的坐标系中是倒过来的,想要图片居顶部需要使用kCAGravityBottom;另外这个属性也有kCAGravityResizeAspect和kCAGravityResizeAspectFill这些值;


    kCAGravityBottom

    5.自定义layer

    - (void)drawInContext:(CGContextRef)ctx;
    

    与drawRect类似,重写这个方法可以在layer上绘制.在drawRect中UIGraphicsGetCurrentContext()获取到的context就是layer层中创建的.

    - (void)drawInContext:(CGContextRef)ctx{
        CGContextMoveToPoint(ctx, 10, 10);
        CGContextAddLineToPoint(ctx, 10, 90);
        CGContextAddLineToPoint(ctx, 90, 50);
        CGContextClosePath(ctx);
        CGContextSetFillColorWithColor(ctx, UIColor.redColor.CGColor);
        CGContextDrawPath(ctx, kCGPathFill);
    }
    
        TestLayer *layer = [TestLayer layer];
        layer.frame = CGRectMake(0, 0, 100, 100);
        layer.backgroundColor = UIColor.lightGrayColor.CGColor;
        [v.layer addSublayer:layer];
        [layer setNeedsDisplay];
    

    setNeedsDisplay与UIView的类似,不过UIView在添加到父视图的时候会自动刷新,或者layer的属性被修改时也会刷新;但刚创建的layer必须主动调用才能立即刷新.
    除此之外还有setNeedsDisplayInRect,对限定区域内进行刷新
    setNeedsDisplay会使layer调用drawInContext,这与layer的contents是互斥的,调用setNeedsDisplay无论有没有重写drawInContext,contents都不会起效.

    + (BOOL)setNeedsDisplayneedsDisplayForKey:(NSString *)key; 
    

    重写这个方法设置某个属性被修改时是否需要刷新


    image.png

    需要注意的是,drawInContext不支持界外的内容,即使masksToBounds是NO

    6.代理

    - (void)displayLayer:(CALayer *)layer;
    - (void)layerWillDraw:(CALayer *)layer;
    - (void)drawLayer:(CALayer *)layer 
            inContext:(CGContextRef)ctx;
    

    第一个方法是当layer要刷新的时候回被调用;第二个方法是drawInContext将要执行的时候,第三个方法是默认的drawInContext执行之后调用,重写drawInContext是不会调用的.
    如果实现第一个方法,后面两个都不会被调用;并且第二个方法的调用发生在第三个方法之前.

    7.反锯齿透明度光栅化缩放
    allowsEdgeAntialiasing 是否开启反锯齿
    allowsGroupOpacity 是否独立透明度,通常透明度会收到superView或者说Superlayer的影响,设置为Yes时,会独立出来,但影响性能.
    edgeAntialiasingMask 对某个边禁用抗锯齿 CAEdgeAntialiasingMask是一个枚举,有上下左右四种
    shouldRasterize 是否需要光栅化:光栅化是图形学术语,在这里它的意思是将layer层生成位图,并且缓存起来,如果后续layer的属性没有变化,或者说计算结果一样,就可以把位图拿出来再用,在频繁重绘的场景下,比如列表布局,使用光栅化是不合适的.
    rasterizationScale 用于设置光栅化bitmap的尺寸比例
    magnificationFilter 图片放大算法模式
    minificationFilter 图片缩小算法模式
    下面是放大的例子

    kCAFilterNearest
    默认

    8.透明度
    先看这样一个例子

    UIView *v1 = [UIView new];
        [self.view addSubview:v1];
        v1.backgroundColor = [UIColor blackColor];
        v1.alpha = .5;
        [v1 mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.equalTo(self.view).offset(20);
            make.trailing.bottom.equalTo(self.view).offset(-20);
            make.height.equalTo(@100);
        }];
        
        UIView *vv = [UIView new];
        [self.view addSubview:vv];
        vv.backgroundColor = [UIColor blackColor];
        vv.alpha = .5;
        [vv mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.top.equalTo(v1).offset(20);
            make.trailing.bottom.equalTo(v1).offset(-20);
        }];
    
    UIView *vvv = [UIView new];
        [self.view addSubview:vvv];
        vvv.backgroundColor = [UIColor blackColor];
        [vvv mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.top.equalTo(v1).offset(40);
            make.trailing.bottom.equalTo(v1).offset(-40);
        }];
    
    透明度是会叠加的
    vv和vvv的父视图是v1的时候

    如果V1是vv的父视图,那么v1的透明度会影响到vv,现在v1和vv是兄弟视图,重叠的部分透明度并不是0.5,是0.5+(0.5*0.5) = 0.75;
    再加一个v2,然后把vv挪一下就能对比出来

    UIView *v2 = [UIView new];
        [self.view addSubview:v2];
        v2.backgroundColor = [UIColor blackColor];
        v2.alpha = .75;
        [v2 mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(v1.mas_top);
            make.width.equalTo(v1).multipliedBy(.5);
            make.height.equalTo(@20);
        }];
    
    透明度是0.75

    如何计算叠加的透明度


    透明度叠加

    9.异步绘制
    drawsAsynchronously 布尔值,是否允许异步绘制,但是这个属性并没有想象中美好, drawInContext仍然是在主线程被调用,Core Graphics操作会加入队列异步执行,而且不是立即执行,可能会延迟,并且会有更多的内存消耗,但是好处是主线程被腾出来做更重要的事,这个过程不是自主控制,如果像下面这么写是没有意义的

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [layer setNeedsDisplay];
        });
    

    10.mask
    mask是CALayer的属性,要了解mask首先看另一个属性masksToBounds

    When the value of this property is YES, Core Animation creates an implicit clipping mask that matches the bounds of the layer and includes any corner radius effects. If a value for the mask property is also specified, the two masks are multiplied to get the final mask value. 
    The default value of this property is NO.
    

    文档中说明masksToBounds是生成一个mask,也就是说用遮罩来达到切除大于layer范围部分的视觉效果.

    @property(strong) __kindof CALayer *mask;
    

    mask也是CALayer,并且可以是CALayer的子类,默认为nil,如果给layer设置mask会怎样

        DrawView5 *v = [[DrawView5 alloc]initWithFrame:CGRectMake(ScreenWidth/4, ScreenHeight/4,         ScreenWidth/2, ScreenHeight/2)];
        v.backgroundColor = [UIColor blackColor];
        [self.view addSubview:v];
        
        TestLayer *layer = [TestLayer layer];
        layer.frame = v.bounds;
        layer.contents = (id)[UIImage imageNamed:@"avatar"].CGImage;
        [v.layer addSublayer:layer];
        
        CALayer *masklayer = [[CALayer alloc]init];
        masklayer.frame  = CGRectMake(0, 0, 100, 100);
        masklayer.backgroundColor = [UIColor whiteColor].CGColor;
        layer.mask = masklayer;
    

    mask并不是真的盖上去一层layer,而是使用alpha通道控制显示,设置mask的frame小于layer,只有左上角一点,看下效果


    image.png

    也就是说超出mask的部分都被切掉了
    由于是根据mask的alpha通道(不透明度)来控制显示效果,所以mask必须是不透明的,默认clearColor所以上面随便设置了一个颜色,如果masklayer的透明度(opacity)在0到1之间,那么显示出来的区域也是有透明度的.

    mask本身是一个CALayer,所以mask也可以有contents,比如一张图,而图片是可以有alpha通道的;
    1.当设置了mask的opacity时,alpha通道就是这个值
    2.alpha是依据背景色和图片本身的透明度来计算的
    3.不管是哪种情况,alpha为0,则layer层内容不显示,alpha为1,则layer层完全显示

    TestLayer *layer = [TestLayer layer];
        layer.frame = v.bounds;
        layer.contents = (id)[UIImage imageNamed:@"avatar"].CGImage;
        layer.contentsGravity = kCAGravityCenter;
    //    layer.masksToBounds = YES;
        layer.backgroundColor = UIColor.lightGrayColor.CGColor;
        layer.delegate = self;
        layer.drawsAsynchronously = YES;
        [v.layer addSublayer:layer];
        
        CALayer *masklayer = [[CALayer alloc]init];
        masklayer.frame  = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.width);
        masklayer.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:.5].CGColor;
    //    masklayer.opacity = .5;
        masklayer.contents = (id)[UIImage imageNamed:@"stw1"].CGImage;
        layer.mask = masklayer;
    
        UIImageView *st = [[UIImageView alloc]initWithFrame:CGRectMake(0, v.bounds.size.width+100, v.bounds.size.width/4, v.bounds.size.width/4)];
        st.image = [UIImage imageNamed:@"stw1"];
        [v addSubview:st];
    

    这个例子里面是把下面的棋子图片作为mask添加到了上面的图片layer上,棋子本身alpha是1,所以中间完全可见,图片中棋子周围虽然alpha是0,但是背景色是0.5的透明度,所以是半透明(图1),如果背景色是透明的,那么周围就不是半透明了,就是完全不可见的(图2)


    图1
    图2

    相关文章

      网友评论

          本文标题:Core Animation 一 : CALayer

          本文链接:https://www.haomeiwen.com/subject/xegtbktx.html