美文网首页iOS动画iOS开发
iOS - Quartz2D & CALayer &am

iOS - Quartz2D & CALayer &am

作者: 零点知晨 | 来源:发表于2016-09-02 12:11 被阅读1196次

    自己学习用的,做一个简单的汇总。


    1.Quartz2D

    提起iOS中的绘图控件,必然会想到Quartz2D。Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统。Quartz2D的API来自于CoreGraphics框架,数据类型和函数基本都以CG作为前缀,如CGContextRef、CGPathRef。
    Quartz 2D能完成的工作:

    • 绘制图形 : 线条\三角形\矩形\圆\弧等
    • 绘制文字
    • 绘制\生成图片(图像)
    • 读取\生成PDF
    • 截图\裁剪图片
    • 自定义UI控件

    一般在开发中,给图片添加水印合并图片自定义UI控件(UIKit框架中没有的)等。
    一般情况下,通过苹果官方的UIKit库就可以搭建常见的UI界面了,但是如果通过UIKit框架不能实现的一些UI界面,就需要我们自己来通过Quartz2D进行绘制了。
    自定义UI控件是Quartz2D中非常重要的一个功能

    使用Quartz2D 绘图有2种方式:
    方式一:直接调用Quartz2D的API 进行绘图
    方式二:调用 UIKit 框架封装好的 API 进行绘图(代码简单,但是只能是文字和图片)

    绘图的基本步骤:
    1.开启图形上下文(如果是自定义view,不需要这一步,但是必须在drawRect方法中获取context,在这里才能获取和view相关联的context)

    UIGraphicsBeginImageContext(size)
    

    2.获取图形上下文

    CGContextRef context = UIGraphicsGetCurrentContext();
    

    3.设置上下文的一些属性(颜色,线宽)

    CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
    CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
    CGContextSetLineWidth(context, 2.0);   
    CGContextSetLineJoin(context, kCGLineJoinRound);// 设置连接处的样式
    CGContextSetLineCap(context, kCGLineCapRound);// 设置头尾的样式
    
    //UIColor本身提供了setStroke,setFill的方法
    [[UIColor purpleColor] setStroke];
    

    4.拼接路径(绘制图形)

    1. 
        CGContextMoveToPoint(context, 50, 50);
        CGContextAddLineToPoint(context, 100, 100);
    
    2.
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, 100, 100);
        CGPathAddLineToPoint(path, NULL, 200, 200);
        CGContextAddPath(context, path);
    
    3.
        UIBezierPath* path = [[UIBezierPath alloc] init];
        [path moveToPoint:CGPointMake(100, 100)];
        [path addLineToPoint:CGPointMake(200, 200)];
        CGContextAddPath(context, path.CGPath);
    
    4.纯OC方法
        // 创建路径对象
        UIBezierPath* path = [UIBezierPath bezierPath];
        // 拼接
        [path moveToPoint:CGPointMake(100, 100)];
        [path addLineToPoint:CGPointMake(200, 200)];
        [path setLineWidth:30];
        [path setLineJoinStyle:kCGLineJoinBevel];
    
        // 通过路径对象 渲染
        [path stroke];
    

    5.渲染

    CGContextStrokePath(context);
    CGContextFillPath(context);
    CGContextEOFillPath(context);
    CGContextDrawPath(context,drawModel);               //很多的模式,stroke、fill....
    CGContextStrokeRectWithWidth(context.lineWidth);//stroke的同时,设置了线宽
    CGContextStrokeLineSegments                              //画线段
    [UIBezierPath stroke];
    

    6.关闭上下文(关闭路径)

    CGContextClosePath(context)
    - (void)closePath;                      //oc
    UIGraphicsEndImageContext()
    

    iOS中提供了绘制各种图形的方法

    1.C代码
    CGContextAddLineToPoint //直线
    CGContextAddCurveToPoint //曲线
    CGContextAddQuadCurveToPoint //四方曲线(暂时先这样翻译吧)
    CGContextAddRect //矩形
    CGContextAddEllipseInRect //椭圆
    CGContextAddArc //圆弧

    2.OC代码(UIBezierPath)

    - (void)moveToPoint:(CGPoint)point;
    - (void)addLineToPoint:(CGPoint)point;
    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
    - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
    

    示例代码:

    1. 创建圆形图片的方法:关于这块有挺多东西的,需要的话可以参考iOS 高效添加圆角效果实战讲解,这篇帖子的评论部分也提供了好多别人的框架
    - (instancetype)circleImage  {
        // 开启图形上下文
        UIGraphicsBeginImageContext(self.size);
        // 获取上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        // 添加一个圆
        CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
        CGContextAddEllipseInRect(ctx, rect);
        // 裁剪
        CGContextClip(ctx);
        // 绘制图片
        [self drawInRect:rect];  
        // 获得图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        // 关闭图形上下文
        UIGraphicsEndImageContext(); 
        return image;
    }
    

    自定义view(自定义UI控件)
    ⾃定义view的步骤:
    (1)新建⼀个类,继承自UIView
    (2)实现-(void)drawRect:(CGRect)rect⽅法

    注:1.绘制的方法必须写在drawRect:方法中,只有在这里才能获取和view相关联的上下文
    2.drawRect:什么时候调用(不能自己调用,只能通过系统调用这个方法)。
    当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
    调用view的setNeedsDisplay或者setNeedsDisplayInRect:时

    自定义圆形进度条(drawRect实现):
    1.进度值 -- 开放一个progressValue属性
    2.根据进度值,重绘UI界面 --[self setNeedsDisplay],然后系统会自动调用drawRect方法

    - (void)setProgressValue:(CGFloat)progressValue {
        _progressValue = progressValue;
        [self setNeedsDisplay];// 重绘
    }
    
    - (void)drawRect:(CGRect)rect {
        UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:self.center radius:self.frame.size.width < self.frame.size.height ? self.frame.size.width * 0.3 : self.frame.size.height * 0.3 startAngle:-M_PI_2 endAngle:2 * M_PI * self.progressValue - M_PI_2 clockwise:1];
    
        [path setLineWidth:3];
        [[UIColor blueColor] setStroke];
        
        [path stroke];// 渲染
    }
    

    注:在drawRect方法中绘图,
    1.不需要创建图形上下文。 -- 这里可以直接获取到和view相关联的上下文
    2.使用UIBezierPath(OC方法),不需要获取图形上下文,内部苹果已经封装了
    3.不需要关闭上下文

    圆形进度条,在这里其实就可以和SDWebImage结合起来,实现一个图片加载的进度显示了。这里贴一个SDWebImage进度显示简单Demo


    2. CALayer

    在iOS中CALayer的设计主要是了为了内容展示动画操作,CALayer本身并不包含在UIKit中,它不能响应事件。由于CALayer在设计之初就考虑它的动画操作功能,CALayer很多属性在修改时都能形成动画效果,这种属性称为“隐式动画属性”。但是对于UIView的根图层而言属性的修改并不形成动画效果,因为很多情况下根图层更多的充当容器的做用,如果它的属性变动形成动画效果会直接影响子图层。另外,UIView的根图层创建工作完全由iOS负责完成,无法重新创建,但是可以往根图层中添加子图层或移除子图层。

    CALayer的基本使用

    1. 设置圆角
    view.layer.cornerRadius = 5;
    view.layer.maskToBound = YES; 
    

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

    1. 设置边框
    view.layer.boarderWidth = 5;
    view.layer.boarderColor = [UIColor redColor].CGColor;
    
    1. 设置旋转
    view.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
    
    1. 设置阴影
    view.layer.shadowColor = [UIColor grayColor].CGColor;
    view.layer.shadowOffset = CGSizeMake(10, 10);
    view.layer.shadowOpacity = 0.5;
    

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

    1. 设置背景图片
    view.layer.content = [UIImage iamgeNamed:@"xxx"].CGImage;
    

    自定义layer

    layer和view是紧密相连的,所以他们的操作很相似。自定义layer需要重写drawInContext方法,自定义view需要重写drawRect方法。针对view的操作最终都会作用到layer上

    - (void)drawInContext:(CGContextRef)ctx {
        // 设置为蓝色
        CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);
        
        //绘制图形的方法和view是一样的都是使用Quartz2D,参考Quartz2D部分
    
        // 渲染
        CGContextFillPath(ctx);
    }
    

    注意:需要刷新控件时,不能自己手动调用这个方法,而是使用[self setNeedsDisplay]

    CALayer的mask属性

    mask属性之前从来没有使用过,感觉很陌生,所以特意找了一下这方面的。使用CALayer的Mask实现注水动画效果这个效果很不错,直接拿过来了

    mask实际上是layer内容的一个遮罩,如果我们把mask设置成透明的,实际看到的layer是完全透明的(这个layer就看不到了),也就是说只有mask的内容不透明的部分和layer叠加的部分才会显示出来,效果如下:

    20141117_02.jpg
    一张图片说明了一切,代码就不贴了,直接参考原作者的吧。
    设计的思路参考《基于Core Animation的KTV歌词视图的平滑实现》,Facebook Shimmer
    20141117_03-2.jpg

    CALayer的动画 -- Core Animation

    这个内容就太多了,动画部分不多说,注意:核心动画作用在layer上。
    贴几篇动画相关文章
    iOS动画-从UIView动画说起
    iOS动画-Transform和KeyFrame动画
    iOS动画-layout动画初体验
    iOS动画-layout动画的更多使用
    iOS动画-碎片动画
    iOS动画-认识CoreAnimation


    3. CAShapLayer

    1. CAShapeLayer继承自CALayer,可使用CALayer的所有属性
    2. CAShapeLayer需要和贝塞尔曲线配合使用才有意义。
      在苹果的General Tips and Tricks可以找到这句话
    The CAShapeLayer class creates its content by rendering the path you provide into a bitmap image at composite time.
    
    1. 使用CAShapeLayer与贝塞尔曲线可以实现不在view的DrawRect方法中画出一些想要的图形
    The CAShapeLayer class draws a cubic Bezier spline in its coordinate space. 
    //CAShapeLayer这个类是用来绘制三次贝塞尔曲线的
    

    CAShapeLayer结合贝塞尔曲线(UIBezierPath)**

    UIBezierPath在Quartz2D部分有介绍,CGPathRef是基于C语言的路径,UIBezierPath是OC版的,用法很类似。他们都是用来绘制曲线

    绘制的方法

    + (instancetype)bezierPath;
    + (instancetype)bezierPathWithRect:(CGRect)rect;
    + (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
    + (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
    + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
    + (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
    + (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
    
    - (void)moveToPoint:(CGPoint)point;
    - (void)addLineToPoint:(CGPoint)point;
    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
    - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
    

    示例代码:

    1. 自定义圆形进度条(CAShapLayer实现):

    CAShapLayer中有两个非常关键的属性strokeEnd,strokeStart。自定义圆形进度条(drawRect实现)中,我们是通过调用[self setNeedDisplay]方法来实现视图的绘制的,但是在CAShapLayer中,直接设置strokeEnd就可以直接自动的去绘制视图更新,不需要我们操作什么,就像设置view的frame,bounds等一样,系统自动帮我们做了。

    Untitled222.gif
    //
    //  CustomProgressLayer.m
    //  CAShapeLayer实现圆形进度条
    //
    //  Created by yangguangyu on 16/8/31.
    //  Copyright © 2016年 yangguangyu. All rights reserved.
    //
    
    #import "CustomProgressLayer.h"
    
    @implementation CustomProgressLayer
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [self setup];
        }
        return self;
    }
    
    -(void)setup {
        self.strokeColor = [UIColor redColor].CGColor;
        self.fillColor = [UIColor clearColor].CGColor;
        self.lineWidth = 5;
        self.strokeStart = 0;
        self.strokeEnd = 0;
    }
    
    
    -(void)addProgressLayerOnView:(UIView *)view {
        self.frame = view.bounds;
        
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:self.frame.size.width * 0.1 startAngle:0 endAngle:2 * M_PI clockwise:YES];
        self.path = path.CGPath;
        
        [view.layer addSublayer:self];
    }
    
    -(void)setProgressValue:(CGFloat)progressValue {
        _progressValue = progressValue;
        
        if (progressValue < 0) {
            progressValue = 0;
        }else if (progressValue > 1) {
            progressValue = 1;
        }
        
        self.strokeEnd = progressValue;
    }
    @end
    

    www.raywenderlich.com官网上,有一个非常不错的圆形图片加载动画

    1434094036762587.gif

    前面我们已经实现了圆环的效果,只需要在图片加载完成的时候添加一个动画

    -(void)reveal {
        self.superlayer.mask = self;
    
        //动画
        CABasicAnimation *lineAnimation = [CABasicAnimation animationWithKeyPath:@"lineWidth"];
    //    lineAnimation.toValue = @(300);//这里决定了中间最后状态时中心圆环的大小
        lineAnimation.toValue = @(self.bounds.size.width);
        
        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
        UIBezierPath *toPath = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
        pathAnimation.toValue = toPath;//这里决定了最外层
    
        CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
        group.animations = @[lineAnimation,pathAnimation];
        group.duration = 2;
        group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        group.removedOnCompletion = NO;
        group.fillMode = kCAFillModeForwards;
        [self addAnimation:group forKey:nil];
    
    }
    

    原版的是swift的,原贴How To Implement A Circular Image Loader Animation with CAShapeLayer,自己用OC也写了一份demo

    2. 绘制虚线(不用CAShaprLayer也可以实现,直接使用Quartz2D)

    /**
     ** lineView:       需要绘制成虚线的view
     ** lineLength:     虚线的宽度
     ** lineSpacing:    虚线的间距
     ** lineColor:      虚线的颜色
     **/
    + (void)drawDashLine:(UIView *)lineView lineLength:(int)lineLength lineSpacing:(int)lineSpacing lineColor:(UIColor *)lineColor
    {
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        [shapeLayer setBounds:lineView.bounds];
        [shapeLayer setPosition:CGPointMake(CGRectGetWidth(lineView.frame) / 2, CGRectGetHeight(lineView.frame))];
        [shapeLayer setFillColor:[UIColor clearColor].CGColor];
         
        //  设置虚线颜色为blackColor
        [shapeLayer setStrokeColor:lineColor.CGColor];
         
        //  设置虚线宽度
        [shapeLayer setLineWidth:CGRectGetHeight(lineView.frame)];
        [shapeLayer setLineJoin:kCALineJoinRound];
         
        //  设置线宽,线间距
        [shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:lineLength], [NSNumber numberWithInt:lineSpacing], nil]];
         
        //  设置路径
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, 0, 0);
        CGPathAddLineToPoint(path, NULL, CGRectGetWidth(lineView.frame), 0);
         
        [shapeLayer setPath:path];
        CGPathRelease(path);
         
        //  把绘制好的虚线添加上来
        [lineView.layer addSublayer:shapeLayer];
    }
    
    

    3. 给UIView添加分隔线

    在很多情况下,我们需要给UIView添加一个分隔线,尤其是使用UITableView的时候,经常会自己搞分割线,以前我的做法是在cell的底部添加一个高度为1的UIView,使用CAShapeLayer或者CALayer可以不用再添加view了,直接搞一个layer上去。写一个UIView的分类

    //
    //  UIView+BorderLine.m
    //  CAShapeLayer分割线
    //
    //  Created by yangguangyu on 16/9/2.
    //  Copyright © 2016年 yangguangyu. All rights reserved.
    //
    
    #import "UIView+BorderLine.h"
    
    
    @implementation UIView (BorderLine)
    
    -(void)addBorderTopLine:(CGSize)size andColor:(UIColor *)color {
        [self addBorderLineWithframe:CGRectMake(0, 0 , size.width, size.height) andColor:color];
    }
    
    -(void)addBorderLeftLine:(CGSize)size andColor:(UIColor *)color {
        [self addBorderLineWithframe:CGRectMake(0, 0, size.width, size.height) andColor:color];
    }
    
    -(void)addBorderBottomLine:(CGSize)size andColor:(UIColor *)color {
        [self addBorderLineWithframe:CGRectMake(0, self.frame.size.height - size.height, size.width, size.height) andColor:color];
    }
    
    -(void)addBorderRightLine:(CGSize)size andColor:(UIColor *)color {
        [self addBorderLineWithframe:CGRectMake(self.frame.size.width - size.width, 0, size.width, size.height) andColor:color];
    }
    
    #pragma mark - padding
    
    
    -(void)addBorderTopLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
        [self addBorderLineWithframe:CGRectMake(padding, 0 , size.width - 2 * padding, size.height) andColor:color];
    }
    
    -(void)addBorderLeftLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
        [self addBorderLineWithframe:CGRectMake(0, padding, size.width, size.height - 2 * padding) andColor:color];
    }
    
    -(void)addBorderBottomLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
        [self addBorderLineWithframe:CGRectMake(padding, self.frame.size.height - size.height, size.width - 2 * padding, size.height) andColor:color];
    }
    
    -(void)addBorderRightLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
        [self addBorderLineWithframe:CGRectMake(self.frame.size.width - size.width, padding, size.width, size.height - 2 * padding) andColor:color];
    }
    
    //-------
    
    -(void)addBorderLineWithframe:(CGRect)frame andColor:(UIColor *)color {
        
        CAShapeLayer *layer = [CAShapeLayer layer];
        layer.frame = frame;
        layer.backgroundColor = color.CGColor;
        [self.layer addSublayer:layer];
    }
    @end
    

    参考文章:

    OS开发UI篇—Quartz2D简单介绍
    使用CALayer的Mask实现注水动画效果
    iOS开发之让你的应用“动”起来崔江涛的教程都很好,讲的非常全面
    How To Implement A Circular Image Loader Animation with CAShapeLayer
    放肆的使用UIBezierPath和CAShapeLayer画各种图形
    关于CAShapeLayer的一些实用案例和技巧
    基于CAShapeLayer和贝塞尔曲线的圆形进度条动画【原创】

    相关文章

      网友评论

      本文标题:iOS - Quartz2D & CALayer &am

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