美文网首页收藏ios
iOS开发经验(8)-绘图

iOS开发经验(8)-绘图

作者: Ryan___ | 来源:发表于2016-10-20 17:16 被阅读402次
    目录:
    1. 主要绘图框架介绍
    2. CALayer
    3. 绘图
    4. 贝塞尔曲线-UIBezierPath
    5. CALayer子类
    6. 补充:iOS角度与弧度转换
    7. 补充:附图
    1. 主要绘图框架介绍
    关系.png
    基本概念

    CoreGraphics,OpenGL,QuartzCore,QuartZ 2D,CoreAnimation几个框架区别不了解,在此根据关系图谱梳理一下:

    ** iOS支持两套图形API族:Core Graphics/QuartZ 2DOpenGL ES。**
    Core Graphics Framework是一套基于CAPI框架,使用了Quartz作为绘图引擎。Core Graphics是一个绘图专用的API族,它经常被称为QuartZQuartZ 2DCore GraphicsiOS上所有绘图功能的基石,包括UIKit。该框架可以用于基于路径的绘图、变换、颜色管理、离屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。它是iOS的核心图形库,类名以CG开头的都属于CoreGraphics框架,它提供的都是C语言的函数接口,是可以在iOSMac OS X通用的。
    QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分。
    OpenGL ES是跨平台的图形API,属于OpenGL的一个简化版本。需要注意的是:OpenGL ES是应用程序编程接口,该接口描述了方法、结构、函数应具有的行为以及应该如何被使用的语义。也就是说它只定义了一套规范,具体的实现由设备制造商根据规范去做。
    QuartzCore:这个框架的名称感觉不是很清晰,不明确,但是看它的头文件可以发现,它其实就是CoreAnimation,封装的CoreAnimation,这个框架的头文件只包含了CoreAnimation.h
    CoreAnimation:核心动画,顾名思义,想要做动画就使用CA开头的就没错。在CoreAnimation框架下,最主要的两个部分是图层CALayer以及动画CAAnimation类。前者管理着一个可以被用来实现动画的位图上下文;后者是一个抽象的动画基类,它提供了对CAMediaTimingCAAction协议的支持,方便子类实例直接作用于CALayer本身来实现动画效果。
    UIView:在iOS当中,所有的视图都从一个叫做UIVIew的基类派生而来,UIView可以处理触摸事件,可以支持基于Core Graphics绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。

    2. CALayer

    什么是Layer
    CALayer包含在QuartzCore框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中。
    CALayerCore Animation操作的对象, 这也是Layer为什么是CA打头的原因;
    CALayer我们又称为图层,在每个UIView内部都有一个layer的属性,UIView之所以能够显示,就是因为它里面有layer层,才具有显示的功能,我们通过操作CALayer对象,可以很方便地调整UIView的一些外观属性,例如可以给UIView设置阴影,圆角,边框等等...

    一般来说,layer可以有两种用途,二者不互相冲突:一是对view相关属性的设置,包括圆角、阴影、边框等参数,更详细的参数请点击这里;二是实现对view的动画操控。因此对一个view进行core animation动画,本质上是对该view的.layer进行动画操纵。

    CALayer和UIView的区别:
    UIView之所以能看得见是因为里面有一个图层(即CALayer对象)。对UIView的位置大小的操作,实际上就是对图层(即CALayer对象)的操作。可以把图层看成是没有事件的UIView,而对应UIView则是这个图层的控制者。
    一般情况下是UIView拥有 CALayerCALayerDelegateUIView
    Layer的设计目的是提供视图的基本可视内容,从而提高动画的执行效率,除提供可视内容外,Layer不负责视图的事件响应、内容绘制等工作,同时Layer不能参与到响应者链条中。

    创建视图对象时,视图会自己创建一个层,视图在绘图(如drawRect:)时,会将内容画在自己的层上。当视图在层上完成绘图后,系统会将图层拷贝至屏幕。每个视图都有一个层,而每个图层又可以有多个子层。

    Layer的基本属性
    1.position跟 anchor point

    position:它是用来设置当前的layer在父控件当中的位置的.所以它的坐标原点.以父控件的左上角为(0.0)点.
    anchorPoint:又称锚点,它是决点CALayer身上哪一个点会在position属性所指的位置。anchorPoint是以当前的layer左上角为原点(0.0),它的取值范围是0~1,默认位置在中间也就是(0.5,0.5)。想要修改某个控件的位置,我们可以设置它的position点.设置完毕后.layer身上的anchorPoint会自动定到position所在的位置。

    2.设置阴影

    //默认图层是有阴影的, 只不过是透明的。1为不透明,0为透明
    _RedView.layer.shadowOpacity = 1;
    //设置阴影的偏移量
    self.imageV.layer.shadowOffset = CGSizeMake(-30, -10);
    //设置阴影的圆角
    _RedView.layer.shadowRadius =10;
    //设置阴影的颜色,把UIKit转换成CoreGraphics框架,用.CG开头
    _RedView.layer.shadowColor = [UIColor blueColor].CGColor;
    

    3.设置边框

    设置图层边框,在图层中使用CoreGraphics的CGColorRef
    //设置边框的颜色
    _RedView.layer.borderColor = [UIColor whiteColor].CGColor;
    //设置边框的宽度
    _RedView.layer.borderWidth = 2;
    

    4.设置圆角

    图层的圆角半径,圆角半径为宽度的一半, 就是一个圆
    _RedView.layer.cornerRadius = 50;
    

    操作layer改变UIImageView的外观 设置图形边框

    //设置边框宽度
    _imageView.layer.borderWidth = 2;
    //设置边框颜色
    _imageView.layer.borderColor = [UIColor whiteColor].CGColor;
    设置图片的圆角半径
    //我们设置的所有layer的属性只作用在根层上,根层设置为圆形后,其上面的图片并不会改变,
    因此需要裁剪
    _imageView.layer.cornerRadius = 50;
    //裁剪,超出裁剪区域的部分全部裁剪掉;如果设置了maskToBounds=YES,那将不会有阴影效果
    _imageView.layer.masksToBounds = YES;
    

    注意:UIImageView当中Image并不是直接添加在根层上面的.而是添加在layer当中的contents层里。我们设置层的所有属性它只作用在根层上面.对contents里面的东西并不起作用。所以我们看不到图片有圆角的效果。想要让图片有圆角的效果.可以把masksToBounds这个属性设为YES.把就会把超过根层以外的东西都给裁剪掉。masksToBounds方法告诉layer将位于它之下的layer都遮盖住,这样会使圆角不被遮,但是这样会导致阴影效果没有,可以再添加一个SubLayer,添加阴影。此处可以和UIViewclipToBounds来比较记忆(clipToBoundsyes会使其上的内容包括子视图不能超出边界)

    photoView.clipsToBounds = YES ;
    

    5. contents 设置寄宿图
    方法1:给contents赋CGImage的值

    #pragma mark - 创建layer
    UIImage *image = [UIImage imageNamed:@"icon1"];
    CALayer *layer = self.view.layer;
    layer.contents = (__bridge id)(image.CGImage);
    layer.contentsGravity = kCAGravityResizeAspect;
    layer.contentsScale = [UIScreen mainScreen].scale;
    layer.contentsCenter = CGRectMake(0.45, 0.45, 0.1, 0.1);
    
    #pragma mark - 创建View
    - (void)creatView
    {
        self.view_test = [[UIView alloc]initWithFrame:CGRectMake(10, 100, 100, 100)];
        self.view_test.backgroundColor = [UIColor redColor];
        self.view_test.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"layerTest"].CGImage);
        [self.view addSubview:self.view_test];
    }
    

    方法2:直接用Core Graphics直接绘制寄宿图。
    能够通过继承UIView并实现-drawRect:方法来自定义绘制。如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法。

    方法3:设置view的backgroundColor。

    self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"me"]];
    **设置图层内容  (将一个UIImage类型转换为CGImageRef)<c对象转换成oc对象>**
    

    6.图层蒙板
    这个属性本身就是个CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,mask图层定义了父图层的部分可见区域。
    图层A有一个属性是mask,mask实际上也是一个图层,该图层设置为图层B。mask层工作原理是按照透明度裁剪,只保留非透明部分,所以图层B并非覆盖在图层A上,而是根据图层B不透明的部分去显示图层A。若图层B是个蓝色圆环,而图层A是个红色的长方形,那么最终显示的就是红色的圆环。

      CALayer *maskLayer = [CALayer layer];
      maskLayer.frame = self.imageView.bounds;
      UIImage *maskImage = [UIImage imageNamed:@"test.png"];
      maskLayer.contents = (__bridge id)maskImage.CGImage;
      self.imageView.layer.mask = maskLayer;
    

    7.layer的CATransform3D属性-图层形变
    x,y,z 分别代表x,y,z轴.
    //注意:只有旋转的时候才可以看出3D的效果.
    旋转:CATransform3DMakeRotation(M_PI, 1, 0, 0);
    平移:CATransform3DMakeTranslation(x,y,z)
    缩放:CATransform3DMakeScale(x,y,z);
    旋转方法1:

        [UIView animateWithDuration:1.0f animations:^{
            redView.layer.transform =
            CATransform3DMakeRotation(M_PI, 1, 1, 0);
        }];
    

    旋转方法2:KVC

        [UIView animateWithDuration:1.0f animations:^{
             [redView.layer setValue:@(M_PI)
             forKeyPath:@"transform.rotation"];
        }];
    

    可以通过KVC的方式进行设置属性,但是CATransform3DMakeRotation的值是一个结构体, 所以要把结构转成对象.

    NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 0, 0)];
    [_imageView.layer setValue:value forKeyPath:@"transform.scale"];
    

    通过KVC改变属性
    除了直接更改layer的属性外,也可以用KVC方法更改属性。
    什么时候用KVC?
    当需要做一些快速缩放,平移,二维旋转时用KVC。
    CoreAnimation使CAAnimationCALayer继承于NSKeyValueCoding协议,这个扩展增加了默认的一些keys对于的value,添加的keyPath包括了CGPoint,CGRect,CGSizeCATransform3D类型。意思就是你可以根据任意的keys来设置对应的value,即使这个key不是CALayer公开的属性,你可以根据下面的方式来设置一个value
    比如:

    // 快速的进行缩放.后面forKeyPath属性值不是乱写的.苹果文档当中给了相关的属性
    [_imageView.layer setValue:@0.5 forKeyPath:@"transform.scale"];
    // 顺时针旋转45
    imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
    

    隐式动画:
    RootLayer:每一个UIView内部都默认关联着一个CALayer,这个CALayerRoot Layer(根层),根图层更多的充当容器的做用,并且UIView的根图层创建工作完全由iOS负责完成,无法重新创建。注意:在正常情况下,对于View中的RootLayer,UIView默认情况下禁止了layer动画,但是在animation block中又重新启用了它们.这便是:当一个属性在动画 block之外被改变时没有动画,但是当属性在动画block内被改变时,就带上了动画的原因。

    非Root Layer:对于所有的非Root Layer,即手动创建的CALayer对象,都存在着隐式动画。当对非Root Layer的部分属性进行相应的修改时,默认会自动产生一些动画效果,这些属性称为Animatable Properties(可动画属性),凡是文档中有Animatable字样的属性都是可动画属性。
    隐式属性动画的本质是这些属性的变动默认隐含了CABasicAnimation动画实现,例如修改bounds属性会产生缩放动画,修改backgroundColor属性会产生背景色的渐变动画,修改position属性会产生平移动画,隐式动画的默认时长为1/4秒

    CALayer可动画属性
    列举常见的Animatable Properties:

    bounds:(缩放动画)用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
    backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
    position:(平移动画)用于设置CALayer的位置。修改这个属性会产生平移动画
    opacity:(改变透明度)淡入淡出动画
    

    注意:CALayer中很少使用frame属性,因为frame本身不支持隐式动画,通常使用boundsposition代替.

    如何取消隐式动画?
    首先要了解动画底层是怎么做的.动画的底层是包装成一个事务来进行的.什么是事务?很多操作绑定在一起,当这些操作执行完毕后,才去执行下一个操作。
    对于独立CALayer而言,隐式动画是默认开启的,只需要更改可动画属性就会触发隐式动画,但对于UIView的Backing Layer而言,隐式动画是默认关闭的。
    当更改独立CALayer的可动画属性时,Core Animation会为我们创建默认的动画对象来实现相应的动画,但Core Animation又以什么作为标准来实现这些动画呢?隐式动画的大部份参数由事务(CATransaction)来决定,其中包括,动画时间,动画缓冲等。
    虽然隐式动画的大部份参数由CATransaction来决定,但我们奇怪地发现,在代码中并没有出现CATransaction,WHY?这是由于CATransaction和NSAutoreleasePool一样,也分为隐式CATransaction和显式CATransaction,而CATransaction对隐式动画的管理方式与NSAutoreleasePool对内存的管理方式也十分相似,[CATransaction begin]方法是CATransaction的开始,而[CATransaction commit]则是CATransaction的结束,中间便是CATransaction的作用域,需要把更改可动画属性的操作放在CATransaction的作用域内,Core Animation才会创建相应的隐式动画。
    因此我们自己开启事务,并在事物中设置没有动画就会隐藏动画了。
    可以通过动画事务(CATransaction)关闭默认的隐式动画效果

    #pragma mark - 关闭默认的隐式动画效果 
    下述代码为CATransaction的显式创建
    显式创建CATransaction常常被用于关闭隐式动画(独立的CALayer对象默认开启隐式动画,需要手动关闭)和调整动画的时间
    // 获取触摸对象
    UITouch *t = touches.anyObject; 
     // 获取手指的位置
    CGPoint p = [t locationInView:t.view]; 
     // 开启事务
     [CATransaction begin];
     // 设置事务没有动画/禁用隐式动画 
     [CATransaction setDisableActions:YES]; 
    //设置动画执行的时长
    [CATransaction setAnimationDuration:2];
    // 更改可动画属性(这里是更改位置)
    self.layer.position = p; 
    // 提交事务
     [CATransaction commit]; 
    

    显式动画
    一般指在CALayer上进行Core Animation动画(属性动画,转场动画,动画组)。
    核心动画提供了一个显示动画模型。该显式动画模型需要你创建一个动画对象,并设置值,显示动画不会开始执行,直到你把该动画应用到某个图层上面。显式动画的基类为CAAnimation,常用的是CABasicAnimation,CAKeyframeAnimation有时候还会使用到CAAnimationGroup,CATransition(注意不是CATransaction,Transition是过渡的意思)。具体看下一篇文章。

    presentationLayer与modelLayer
    显式动画跟修改CALayer内部属性逐渐变换是不一样的,显式动画修改的是presentationLayer的数据,修改CALayer内部属性逐渐变换是修改modelLayer的数据。presentationLayerCALayer眼睛看到屏幕上CALayer的位置,而实际上CALayer还在modelLayer位置上。这也就是为什么显式动画结束后,如果不使用代码,CALayer还会回到原来位置,因为modelLayer的数据并没有修改。

    显隐式动画区别
    不管是显式动画还是隐式动画,都是基于CAAnimation来实现的,所以这两种动画形式的主要差异在于,是否显式创建CAAnimation对象来实现动画,当然它们还存在其它的差异,例如,隐式动画是基于CABasicAnimation对来实现的(注意不同点:modelLayer被真正修改了),而显式动画可以有更多的选择,可以是CABasicAnimation,CAKeyframeAnimation,CATransition等。

    总结:

    • Core Animation默认对所有东西都做动画,但是隐式动画被UIKit禁用掉。
    • UIKit包括事件处理及CALayer,它拥有自己的一些属性,比如frame,backgroundColor,这些其实是对CALayer的属性的封装,其中比较关注的是2d仿射属性及maskview属性。我们可以其用封装好的属性或者其Layer根图层的属性对View进行修改。无论是改变了此view的属性还是它的layer的属性,这些都真实改变了此view的数据(modelLayer的数据修改了),而且不会引起动画。
    • 对于独立Layer,修改它的属性,真实的改变了它的modelLayer的数据,部分属性会有隐式动画,如果想关闭可以用事务来关闭。其中比较关注的是3d仿射属性(特别是旋转-m.34)、Mask图层蒙版属性、SubLayer以及贝塞尔曲线;
    • 如果Layer是UIView持有的,对Layer操作就是对UIView的操作。CALayer所拥有的属性、方法、协议对持有它的UIView同样有用。

    关于动画
    对UIView进行动画,可以通过block方式,在Block里改变属性,或者改变Layer根图层的属性,可生成动画;也可以用Core Animation,对其持有的Layer进行核心动画操作(属于显式动画,更改的是presentationLayer数据)。

    对CALayer进行动画,可以用隐式动画(),也可以用Core Animation,其实隐式动画是基于核心动画中的CABasicAnimation的(注意不同点:modelLayer被真正修改了)、(属于显式动画,更改的是presentationLayer数据)

    核心动画的对象是CALayer,无论是独立的Layer还是UIView,都可以进行核心动画操作。

    3. 绘图

    使用Core Graphics绘图的步骤:

    • 获取上下文(画布)
    • 创建路径(自定义或者调用系统的API)并添加到上下文中。
    • 进行绘图内容的设置(画笔颜色、粗细、填充区域颜色、阴影、连接点形状等)
    • 开始绘图(CGContextDrawPath
    绘图步骤.png
    图形上下文
    Core Graphics API所有的操作都在一个上下文中进行。所以在绘图之前需要获取该上下文并传入执行渲染的函数中。获得一个图形上下文是我们完成绘图任务的第一步,你可以将图形上下文理解为一块画布。如果你没有得到这块画布,那么你就无法完成任何绘图操作。当然,有许多方式获得一个图形上下文,这里我介绍两种最为常用的获取方法。

    UIView的详细显示过程
    UIView需要显示时,它内部的图层会准备好一个CGContextRef(图形上下文),然后调用delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象。而UIViewdrawLayer:inContext:方法中又会调用自己的drawRect:方法。
    平时在drawRect:中通过UIGraphicsGetCurrentContext()获取的就是由图层传入的CGContextRef对象,在drawRect:中完成的所有绘图都会填入图层的CGContextRef中,然后被拷贝至屏幕。
    view层会调用drawRect:方法从新绘制新的视图,但是这个方法是用CPU在主线程重新绘制了视图,会导致内存消耗过大。

    什么时候需要绘图:
    所需要的设计样子,在目前UIKit框架下无法满足设计图的情况下,可以自己画出复杂的样式。
    可以实现复杂的样式,比如线条、矩形,圆形 椭圆形,不规则图形、可以赋值文字,赋值图片等。

    获取&创建图形上下文的4种方式:

    注:优先级-displayLayer: > -drawInContext: > -drawLayer:inContext: > -drawRect:

    // 1. UIView,在drawRect中,Cocoa会为你创建一个图形上下文
    - (void)drawRect:(CGRect)rect
    // 2. CALayer
    - (void)drawInContext:(CGContextRef)ctx
    // 3. delegate回调
    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
    // 4. 创建图片类型的上下文
    UIGraphicsBeginImageContextWithOptions
    

    UIGraphicsBeginImageContextWithOptions函数创建的上下文适用于图像操作,并且该上下文属于当前上下文,你也可以通过UIGraphicsGetCurrentContext函数获得当前图形上下文
    drawRect方法调用时,Cocoa创建的上线属于当前图形上下文,你也可以通过UIGraphicsGetCurrentContext函数获得当前图形上下文
    delegate回调所持有的context,只是对一个图形上下文的引用,并不一定是当前上下文

    一共有4种使用context的场景
    场景一:使用UIKit框架下的方法,新建一个文件,继承UIView,重写-(void)drawRect:
    1.1:UIKit的OC方法

    #import "DrawRect_OC.h"
    @implementation DrawRect_OC
    // 可省略
    //- (instancetype)initWithFrame:(CGRect)frame
    //{
    //    self = [super initWithFrame:frame];
    //    if (self) {
    //
    //
    //    }
    //    return self;
    //}
    
    - (void)drawRect:(CGRect)rect {
        [super drawRect:rect];
        //绘制出圆了
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
        //设置填充颜色
        UIColor *fillColor = [UIColor redColor];
        [fillColor set];
        [path fill];
        //设置画笔颜色
        UIColor *stokeColor = [UIColor greenColor];
        [stokeColor set];
        [path stroke];
    }
    

    1.2:使用Core Graphics框架下的方法:

    #import "DrawRect_CG.h"
    
    @implementation DrawRect_CG
    
    - (void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
        //获取ctx
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        //设置画图相关样式参数
        
        //设置笔触颜色
        CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
        //设置笔触宽度
        CGContextSetLineWidth(ctx, 2);
        //设置填充色
        CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor);
        //设置拐点样式
        //    enum CGLineJoin {
        //        kCGLineJoinMiter, //尖的,斜接
        //        kCGLineJoinRound, //圆
        //        kCGLineJoinBevel //斜面
        //    };
        CGContextSetLineJoin(ctx, kCGLineJoinRound);
        //Line cap 线的两端的样式
        //    enum CGLineCap {
        //        kCGLineCapButt,
        //        kCGLineCapRound,
        //        kCGLineCapSquare
        //    };
        CGContextSetLineCap(ctx, kCGLineCapRound);
        
        
        [self drawSharpWithctx:ctx Rect:rect];//画矩形、椭圆形、多边形
    //    [self drawPictureWithctx:ctx Rect:rect];//画图片
    //    [self drawTextWithctx:ctx Rect:rect];//画文字
    
        
        
    }
    
    #pragma mark - 画矩形、椭圆形、多边形
    -(void)drawSharpWithctx:(CGContextRef)ctx Rect:(CGRect)rect{
        
    //    //画椭圆,如果长宽相等就是圆
        CGContextAddEllipseInRect(ctx, rect);
        
        
    //    //画矩形,长宽相等就是正方形
    //    CGContextAddRect(ctx, CGRectMake(5, 5, rect.size.width-5-5, rect.size.width-5-5));
        
        
        //画多边形,多边形是通过path完成的
    //    CGMutablePathRef path = CGPathCreateMutable();
    //    CGPathMoveToPoint(path, &CGAffineTransformIdentity, 1, 1);
    //    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, 1);
    //    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, rect.size.width-1-1);
    //    CGPathCloseSubpath(path);
    //    CGContextAddPath(ctx, path);
        
        //填充
        CGContextFillPath(ctx);
    }
    
    
    #pragma mark - 画图片
    -(void)drawPictureWithctx:(CGContextRef)ctx Rect:(CGRect)rect
    {
        /*图片*/
        UIImage *image = [UIImage imageNamed:@"layerTest"];
        [image drawInRect:rect];//在坐标中画出图片
    }
    
    #pragma mark - 画文字
    -(void)drawTextWithctx:(CGContextRef)ctx Rect:(CGRect)rect
    {
        //文字样式
        UIFont *font = [UIFont systemFontOfSize:18];
        NSDictionary *dict = @{NSFontAttributeName:font,
                               NSForegroundColorAttributeName:[UIColor whiteColor]};
        [@"hello world" drawInRect:rect withAttributes:dict];
    }
    

    画矩形、椭圆形、多边形

    #pragma mark - 画矩形、椭圆形、多边形
    -(void)drawSharpWithctx:(CGContextRef)ctx Rect:(CGRect)rect{
        
    //    //画椭圆,如果长宽相等就是圆
        CGContextAddEllipseInRect(ctx, rect);
        
        
    //    //画矩形,长宽相等就是正方形
    //    CGContextAddRect(ctx, CGRectMake(5, 5, rect.size.width-5-5, rect.size.width-5-5));
        
        
        //画多边形,多边形是通过path完成的
    //    CGMutablePathRef path = CGPathCreateMutable();
    //    CGPathMoveToPoint(path, &CGAffineTransformIdentity, 1, 1);
    //    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, 1);
    //    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, rect.size.width-1-1);
    //    CGPathCloseSubpath(path);
    //    CGContextAddPath(ctx, path);
        
        //填充
        CGContextFillPath(ctx);
    }
    

    画图片

    #pragma mark - 画图片
    -(void)drawPictureWithctx:(CGContextRef)ctx Rect:(CGRect)rect
    {
        /*图片*/
        UIImage *image = [UIImage imageNamed:@"layerTest"];
        [image drawInRect:rect];//在坐标中画出图片
    }
    

    画文字

    #pragma mark - 画文字
    -(void)drawTextWithctx:(CGContextRef)ctx Rect:(CGRect)rect
    {
        //文字样式
        UIFont *font = [UIFont systemFontOfSize:18];
        NSDictionary *dict = @{NSFontAttributeName:font,
                               NSForegroundColorAttributeName:[UIColor whiteColor]};
        [@"hello world" drawInRect:rect withAttributes:dict];
    }
    

    场景二:在drawLayer:inContext:代理方法中实现操作

    @interface ViewController ()<CALayerDelegate> //遵守协议
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];    
        //在自定义Layer中:- (void)drawInContext:(CGContextRef)ctx
    //    [self drawInContext_CG];//注意:setNeedsDisplay
    }
    #pragma mark - 代理方法 
    - (void)drawLayer_CG_VC
    {
        CALayer *layer = [CALayer layer];
        layer.bounds = CGRectMake(0, 0, 100, 100);
        layer.position = CGPointMake(K_width/2, K_height/2);
        layer.backgroundColor = [UIColor blueColor].CGColor;
        layer.delegate = self; //设置代理
        [layer setNeedsDisplay];// 调用此方法,drawLayer: inContext:方法才会被调用。
        [self.view.layer addSublayer:layer];
    }
    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
    {
        //使用Core Graphics实现-CG-1
        // 1.画一个圆
    //    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
    //    // 填充颜色为红色
    //    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    //    // 在context上绘制
    //    CGContextFillPath(ctx);
        
        //使用Core Graphics实现-CG-2
        // 2.画一个椭圆
    //    CGContextAddEllipseInRect(ctx, CGRectMake(0,0,100,100));
    //    //填充颜色为蓝色
    //    CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
    //    //在context上绘制
    //    CGContextFillPath(ctx);
        
        // 使用UIKit实现-1
        //使用UIKit进行绘制,因为UIKit只会对当前上下文栈顶的context操作,所以要把形参中的context设置为当前上下文
    //    UIGraphicsPushContext(ctx);
    //    UIImage* image = [UIImage imageNamed:@"test.png"];
    //    //指定位置和大小绘制图片
    //    [image drawInRect:CGRectMake(0, 0,100 , 100)];
    //    UIGraphicsPopContext();
    }
    

    场景三:使用自定义图层绘制,直接绘制到图层。
    编写一个类继承于CALayer然后在drawInContext:中使用CGContext绘图。同样需要调用setNeedDisplay方法。此方法由于并非UIKit直接调用,因此只能用原生的Core Graphics方法绘制。

    #import "CustomLayer_CG.h"
    
    @implementation CustomLayer_CG
    - (void)drawInContext:(CGContextRef)ctx
    {
        // 红色
        CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
        // 添加圆
        CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
        // 实心绘制
        CGContextFillPath(ctx);
    }
    @end
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor]; 
        //在自定义Layer中:- (void)drawInContext:(CGContextRef)ctx
    //    [self drawInContext_CG];//注意:setNeedsDisplay
    }
    #pragma mark -  drawInContext
    - (void)drawInContext_CG
    {
        CustomLayer_CG *layer = [CustomLayer_CG layer];
        layer.bounds = CGRectMake(0, 0, 100, 100);
        layer.position = CGPointMake(K_width/2, K_height/2);
        layer.backgroundColor = [UIColor blueColor].CGColor;
        // 有这句话才能执行 -drawInContext 和 drawRect 方法
        [layer setNeedsDisplay];
        [self.view.layer addSublayer:layer];
    }
    

    场景四:通过自己创建一个context来绘制,通常用于对图片的处理。
    4.1:使用UIKit实现:
    解释一下UIGraphicsBeginImageContextWithOptions函数参数的含义:第一个参数表示所要创建的图片的尺寸;第二个参 数用来指定所生成图片的背景是否为不透明,如上我们使用YES而不是NO,则我们得到的图片背景将会是黑色,显然这不是我想要的;第三个参数指定生成图片 的缩放因子,这个缩放因子与UIImage的scale属性所指的含义是一致的。传入0则表示让图片的缩放因子根据屏幕的分辨率而变化,所以我们得到的图 片不管是在单分辨率还是视网膜屏上看起来都会很好。

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
    UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];
    [p fill];
    UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    

    4.2:使用Core Graphics实现:

        //该函数会自动创建一个context,并把它push到上下文栈顶,坐标系也经处理和UIKit的坐标系相同
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextAddEllipseInRect(context, CGRectMake(0,0,100,100));
        //填充颜色为蓝色
        CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
        //在context上绘制
        CGContextFillPath(context);
        //把当前context的内容输出成一个UIImage图片
        UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
        //上下文栈pop出创建的context
        UIGraphicsEndImageContext();
        [image drawInRect:CGRectMake(0, 0, 100, 100)];
    
    4. 贝塞尔曲线-UIBezierPath

    绘图模块中,知道了不使用图片的情况下用CGPath去构造任意形状的阴影。如果我们能用同样的方式创建相同形状的图层就好了。
    使用UIBezierPath类可以创建基于矢量的路径,这个类在UIKit中。此类是Core Graphics框架关于path的一个封装。使用此类可以定义简单的形状,如椭圆或者矩形,或者有多个直线和曲线段组成的形状。
    UIBezierPath对象是CGPathRef数据类型的封装。path如果是基于矢量形状的,都用直线和曲线段去创建。我们使用直线段去创建矩形和多边形,使用曲线段去创建弧(arc),圆或者其他复杂的曲线形状。
    每一段都包括一个或者多个点,绘图命令定义如何去诠释这些点。每一个直线段或者曲线段的结束的地方是下一个的开始的地方。
    每一个连接的直线或者曲线段的集合成为subpath。一个UIBezierPath对象定义一个完整的路径包括一个或者多个subpaths。

    (1)创建一个Bezier path对象。
    (2)使用方法moveToPoint:去设置初始线段的起点。
    (3)添加line或者curve去定义一个或者多个subpaths,添加线或者曲线去定义一个或者多个子路径
    (4)改变UIBezierPath对象跟绘图相关的属性。
    

    CAShapeLayer与UIBezierPath的关系:

    • 1.CAShapeLayer中shape代表形状的意思,所以需要形状才能生效
    • 2.贝塞尔曲线可以创建基于矢量的路径,而UIBezierPath类是对CGPathRef的封装
    • 3.贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染。路径会闭环,所以绘制出了Shape
    • 4.用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线.
      如何在实践使用:
      第一种写在drawRect中
    #import "PathViewByBezier.h"
    
    @implementation PathViewByBezier
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor redColor];
        }
        return self;
    }
    
    - (void)drawRect:(CGRect)rect {
        [super drawRect:rect];
        [self drawCiclePath];
    }
    
    - (void)drawCiclePath {
        // 传的是正方形,因此就可以绘制出圆了
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.width - 40)];
        
        // 设置填充颜色
        UIColor *fillColor = [UIColor greenColor];
        [fillColor set];
        [path fill];
        
        // 设置画笔颜色
        UIColor *strokeColor = [UIColor blueColor];
        [strokeColor set];
    
        // 根据我们设置的各个点连线
        [path stroke];
    }
    @end
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self creatPathView];
    }
    
    - (void)creatPathView
    {
        PathViewByBezier *pathView = [[PathViewByBezier alloc]initWithFrame:CGRectMake(0, 100, 100, 100)];
        [self.view addSubview:pathView];
    }
    

    第二种在接下来讲到的CAShapeLayer中一块使用.(使用CAShapeLayer与贝塞尔曲线可以实现不在view的DrawRect方法中画出一些想要的图形)**

    - (void)setShapeLayer {
        
        /*
        //   创建折线
        UIBezierPath *path = [UIBezierPath bezierPath];
        //  添加路径起点
        [path moveToPoint:(CGPoint){20,20}];
        // 添加路径之外的其他点
        [path addLineToPoint:(CGPoint){160,160}];
        [path addLineToPoint:(CGPoint){180,50}];
        [path closePath];  // 关闭路径  添加一个结尾和起点相同的点
         */
        
        
        /*
        //创建圆
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:(CGRect){0,0,100,100}];
         */
    
        
         /*
        //创建椭圆
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:(CGRect){0,0,180,100}];
          */
    
        
         /*
        //创建矩形
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 100, 80)];
          */
    
        
         /*
        //创建圆角矩形
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:(CGRect){0,0,200,200} cornerRadius:30];
          */
        
    
        /*
        //画单角的圆角矩形
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:(CGRect){0,0,200,200} byRoundingCorners:UIRectCornerTopLeft cornerRadii:(CGSize){30,0}];
         */
        
        
        /*
        //圆弧
         Center : 圆点
         radius :半径
         startAngle :开始画弧的起始角度; 0度:右 90度:下 180度;左 270度:上
         endAngle :终点画弧的终点角度
         M_PI : 转化为180度角
         M_PI_2 : 转化为90度角
         clockwise : 是否为顺时针方向
         
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:M_PI_2+M_PI endAngle:M_PI clockwise:YES];
        */
        
        
        /*
        //折线和弧线构成的曲线
        //创建路径
        UIBezierPath *path = [UIBezierPath bezierPath];
        //折线
        [path moveToPoint:(CGPoint){0,0}];
        [path addLineToPoint:CGPointMake(100, 100)];
         //添加一条弧线:在原有的线上添加一条弧线
        [path addArcWithCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI clockwise:YES];
        */
    
        
        /*
        //二次贝塞尔曲线
         moveToPoint :起点
         ToPoint : 终点
         controlPoint : 控制点
         曲线是由起点趋向控制点最后到达终点(不会经过控制点)的曲线。控制点决定曲线的起始方向,起点和终点的距离决定曲线趋向控制点的程度
        UIBezierPath *path = [UIBezierPath bezierPath];
        
        [path moveToPoint:(CGPoint){0,150}];
        [path addQuadCurveToPoint:(CGPoint){200,200} controlPoint:(CGPoint){100,0}];
         */
        
        /*
    
         //三次贝塞尔曲线
         UIBezierPath *path = [UIBezierPath bezierPath];
         [path moveToPoint:(CGPoint){0,150}];
         [path addCurveToPoint:(CGPoint){200,50} controlPoint1:CGPointMake(50, 75) controlPoint2:CGPointMake(150, 125)];
        */
    
        UIBezierPath *path = [UIBezierPath bezierPath];
    
        //  设置路径画布
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.backgroundColor = [UIColor yellowColor].CGColor;
        shapeLayer.frame = (CGRect){50,100,200,200};
        shapeLayer.lineWidth = 2.0;
        shapeLayer.strokeColor = [UIColor orangeColor].CGColor; //   边线颜色
        
        shapeLayer.path = path.CGPath;
        shapeLayer.fillColor  = [UIColor greenColor].CGColor;   //  填充颜色,默认是black
        
        //  添加到图层上
        [self.view.layer addSublayer:shapeLayer];
    //    self.view.layer.mask = shapeLayer;  // layer 的 mask属性,添加蒙版
    
    }
    
    5. CALayer子类

    ** 形状图层CAShapeLayer** - 绘制不规则图形
    CAShapeLayer继承自CALayer,因此,可使用CALayer的所有属性。而单独使用CAShapeLayer是没有任何意义的。
    使用CAShapeLayer与贝塞尔曲线可以实现不在view的DrawRect方法中画出一些想要的图形,UIBezierPath只是告诉路径给CAShapeLayer,具体这个shape什么样子由CAShapeLayer来决定。
    CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。
    你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:
    CAShapeLayer和drawRect的比较(可与上述代码联系到一块)

    • 1.drawRect:属于CoreGraphics框架,占用CPU,性能消耗大
    • 2.CAShapeLayer:属于CoreAnimation框架,通过GPU来渲染图形,节省性能。动画渲染直接提交给手机GPU,不消耗内存.
      主要属性
    // CAShapeLayer 绘制的路径
    @property CGPathRef path;表示路径,可以用贝塞尔曲线,也可以自定义路径
    //路径中的填充颜色
    @property(nullable) CGColorRef fillColor;//无动画
    //画笔颜色(路径的颜色,边框颜色)
    @property(nullable) CGColorRef strokeColor; //有动画
    //这是一组范围值,路径绘制开始和结束的范围(0 -> 1)
    @property CGFloat strokeStart;
    @property CGFloat strokeEnd;
    //以下属性参见 UIBezierPath 的介绍
    @property CGFloat lineWidth;线宽,用点表示单位
    @property CGFloat miterLimit;
    @property(copy) NSString lineCap;线条结尾的样子
    @property(copy) NSString *lineJoin;线条之间的结合点的样子
    //填充规则
    @property(copy) NSString fillRule;
    //设置虚线显示的起点距离,设置为8,则显示长度8之后的线
    @property CGFloat lineDashPhase;
    //设置虚线线段的长度和空格的长度,@[@20,@30,@40,@50],画20空30画40空50
    @property(nullable, copy) NSArray<NSNumber > lineDashPattern;
    
    - (void)creatshapeLayer
    {
        CALayer *layer =[CALayer layer] ;
        //设置layer的frame,会改变贝塞尔曲线的frame,它根据layer的frame而改变,也就是说它作为子layer存在(在坐标系统上是存在这种父子关系的,其他不是)
        layer.frame = CGRectMake(50, 250, 150, 150);
        layer.backgroundColor = [UIColor purpleColor].CGColor;
        [self.view.layer addSublayer:layer];
        
        self.shapeLayer =[CAShapeLayer layer] ;
        self.shapeLayer.frame = CGRectMake(0, 0, 150, 150);
        [layer addSublayer:self.shapeLayer];
        
        // bezierPathWithOvalInRect:xy坐标也是从左上开始,150不是半径!是宽度跟高度
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(75/2, 75/2, 75, 75)];
        
        self.shapeLayer.path = path.CGPath;
        //fillcolor没有动画;strokeColor有动画,这样只能在strokeColor上考虑动画了
        self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
        //重点在线宽
        self.shapeLayer.lineWidth = 75;
        self.shapeLayer.strokeColor = [UIColor redColor].CGColor;
        
        layer.mask = self.shapeLayer;
        
        CABasicAnimation *anmation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        anmation.fromValue = @0.0;
        anmation.toValue = @1;
        anmation.duration = 2.5;
        anmation.repeatCount = MAXFLOAT;
        [self.shapeLayer addAnimation:anmation forKey:@""];
    /*    
       总结:frame的影响
        mask父影响-》masklayer影响-》贝塞尔曲线 -进而决定了shapelayer的最终形状。
        不过要处理好,我们不能创建的CAShapeLayer的frame小于贝塞尔曲线 因为如果这样只要设置了masksToBounds这个属性会把超出部分截掉。
    */
    }
    

    ** 渐变图层CAGradientLayer - 颜色渐变、阴影**
    继承calayer,主要用于处理颜色渐变的图层。CAGradientLayer作为CALayer的子类,唯一的用途就是用来做颜色渐变,梯度动画效果等。

    1.CAGradientLayer是用于处理渐变色的层结构
    ,渐变色可以做隐式动画.

    2.大部分情况下, CAGradientLayer都是和CAShapeLayer相互配合使用(CAShapeLayer提供形状, CAGradientLayer提供背景).
    3.CAGradientLayer可以用作png图片的遮罩效果.

    CAGradientLayer的**坐标系统
    **

    a. CAGradientLayer的坐标系统是从坐标(0, 0) 到(1, 1)绘制的矩形

    b. CAGradientLayer的frame的值的size不为正方形的话, 坐标系统会被拉伸

    c. CAGradientLayer的startPoint与endPoint会直接影响颜色的绘制方向

    d. CAGradientLayer的颜色分割点是以0到1的比例来计算的

    #pragma mark - 创建gradientLayer 颜色渐变-滑动解锁
    
    /*
     文字渐变实现思路:
     
     1.创建一个颜色渐变层,渐变图层跟文字控件一样大。
     
     2.用文字图层裁剪渐变层,只保留文字部分,就会让渐变层只保留有文字的部分,相当于间接让渐变层显示文字,我们看到的其实是被裁剪过后,渐变层的部分内容。
     
     注意:如果用文字图层裁剪渐变层,文字图层就不在拥有显示功能,这个图层就被弄来裁剪了,不会显示,在下面代码中也会有说明。
     
     2.1 创建一个带有文字的label,label能显示文字。
     
     2.2 设置渐变图层的mask为label图层,就能用文字裁剪渐变图层了。
     
     3.mask图层工作原理: 根据透明度进行裁剪,只保留非透明部分,显示底部内容。
     */
    - (void)creatgradientLayer
    {
        self.view.backgroundColor=[UIColor grayColor];
        
        CAGradientLayer *gradientLayer=[CAGradientLayer layer];
        
        gradientLayer.frame=CGRectMake(30, 200, 200, 66);
        
        //颜色分布范围--渐变颜色的数组
    //    gradientLayer.colors=@[
    //                          
    //                          (__bridge id)[UIColor blackColor].CGColor,
    //                          
    //                          (__bridge id)[UIColor whiteColor].CGColor,
    //                          
    //                          (__bridge id)[UIColor blackColor].CGColor
    //                          
    //                          ];
        gradientLayer.colors=@[
                              
                              (__bridge id)[UIColor redColor].CGColor,
                              
                              (__bridge id)[UIColor purpleColor].CGColor,
                              
                              (__bridge id)[UIColor cyanColor].CGColor
                              
                              ];
    
        //设置好 colors 要设置好与之相对应的 locations 值
    
        gradientLayer.locations=@[@.25,@.5,@.75];
        //CAGradientLayer 默认的渐变方向是从上到下,即垂直方向。
        //映射locations中第一个位置,用单位向量表示,比如(0,0)表示从左上角开始变化。默认值是(0.5,0.0)
        //映射locations中最后一个位置,用单位向量表示,比如(1,1)表示到右下角变化结束。默认值是(0.5,1.0)。
        //如果要改变 CAGradientLayer 的渐变方向,则要显式的给 startPoint和 endPoint 两个属性赋值。两个属性共同决定了颜色渐变的方向,如果要改为水平方向,则需要改成:
    
        gradientLayer.startPoint=CGPointMake(0, 0.5);
        
        gradientLayer.endPoint=CGPointMake(1., 0.5);
        
        [self.view.layer addSublayer:gradientLayer];
    
        
        
        //label文字:文字是模型 提供轮廓
        
        UILabel *label=[[UILabel alloc]initWithFrame:gradientLayer.bounds];
        // 疑问:label只是用来做文字裁剪,能否不添加到view上。
        // 必须要把Label添加到view上,如果不添加到view上,label的图层就不会调用drawRect方法绘制文字,也就没有文字裁剪了。
        // 如何验证,自定义Label,重写drawRect方法,看是否调用,发现不添加上去,就不会调用
        [self.view addSubview:label];
        
        label.alpha=0.5;
        
        label.text=@"滑动解锁 >>";
        
        label.textAlignment=NSTextAlignmentCenter;
        
        label.font=[UIFont boldSystemFontOfSize:30];
        // mask层工作原理:按照透明度裁剪,只保留非透明部分,文字就是非透明的,因此除了文字,其他都被裁剪掉,这样就只会显示文字下面渐变层的内容,相当于留了文字的区域,让渐变层去填充文字的颜色。
        // 设置渐变层的裁剪层  注意:一旦把label层设置为mask层,label层就不能显示了,会直接从父层中移除,然后作为渐变层的mask层
        gradientLayer.mask=label.layer;
        
        // 添加色变动画:将keyPath赋值为“locations”是让CAGradientLayer的locations属性做动画,因为locations对应着颜色,那么颜色也会跟着动,最终的显示效果就是:
        CABasicAnimation *theAnima=[CABasicAnimation animationWithKeyPath:@"locations"];
        
        theAnima.fromValue=@[@0,@0,@0.25];
        
        theAnima.toValue=@[@0.25,@1,@1];
        
        theAnima.duration=2.5;
        
        theAnima.repeatCount=HUGE;
    
        [gradientLayer addAnimation:theAnima forKey:@"locations"];
        
    }
    

    复制图层 CAReplicatorLayer - 迭代复制同一个图层
    CAReplicatorLayer能复制子层。也就是说,想要复制层,必须先让这个层成为CAReplicatorLayer的子层sublayer。
    CAReplicatorLayer可以对它自己的子Layer进行复制操作。创建了CAReplicatorLayer实例后,设置了它的尺寸大小、位置、锚点位置、背景色,并且将它添加到了replicatorAnimationView的Layer中。

    instanceCount: 要创建的副本个数(默认一个),复制出来的层的个数,包括自己.但是复制子层形变(不包括原生子层),
    **preservesDepth: ** 是否将3D例子系统平面化到一个图层(默认NO)
    instanceDelay: 动画时间延迟,以造成动画的效果。默认为0。复制出来的层的动画相对前一个延迟0.5秒。
    instanceTransform: 迭代图层的位置 CATransform3D对象(创建方法用:CATransform3DMakeRotation圆形排列,CATransform3DMakeTranslation水平排列)。可以是位移,旋转,缩放。复制出来的层相对上一个做操作。子layer相对于前一个复制layer的形变属性。 变换是逐步增加的,每个实例都是相对于前一实例布局。这就是为什么这些复制体最终不会出现在同意位置上.
    instanceColor: 子层颜色,会和原生子层背景色冲突,因此二者选其一设置。
    instanceRedOffset,instanceGreenOffset,instanceBlueOffset,instanceAlphaOffset: 设置颜色通道偏移量,相对上一个。

    注意:
    1.一定要设置CAReplicatorLayer的frame,因为instanceTransform属性都是根据frame的center作为基准的。
    2.复制图层有一个关键的地方是,你往原始的图层中添加动画效果,复制出来的副本可以实时的复制这些动画效果。

    #pragma mark - 创建replicatorLayer  - 4 - 正方形移动的等待指示器
    - (void)creatreplicatorLayer_four
    {
        CGFloat radius = 15;
        CGFloat transX = radius + 5;
        CAShapeLayer *baseLayer = [CAShapeLayer layer];
        baseLayer.frame = CGRectMake(0, 100, radius, radius);
        baseLayer.fillColor = [UIColor redColor].CGColor;
        baseLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, radius, radius)].CGPath;
        
        //横向创建5个圆形baseLayer
        CAReplicatorLayer *replicator1 = [CAReplicatorLayer layer];
        replicator1.frame = CGRectMake(100, 100, radius, radius);
        replicator1.instanceCount = 5;
        replicator1.instanceTransform = CATransform3DMakeTranslation(transX, 0, 0);
        replicator1.instanceDelay = 0.3; //这个属性很重要,设置延迟时间才会出现逐个变化的效果
        [replicator1 addSublayer:baseLayer];
        
        //将上面创建的replicator1 作为子视图在纵向创建5个,形成一个正方形。
        CAReplicatorLayer *replicator2 = [CAReplicatorLayer layer];
        replicator1.frame = CGRectMake(100, 100+radius+10, radius, radius);
        replicator2.instanceCount = 5;
        replicator2.instanceTransform = CATransform3DMakeTranslation(0, transX, 0);
        replicator2.instanceDelay = 0.3;
        [replicator2 addSublayer:replicator1];
        [self.view.layer addSublayer:replicator2];
        
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
        animation.duration = 1;
        animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 0)];
        animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 0.2, 0)];
        animation.repeatCount = HUGE;
        animation.autoreverses = YES; //autoreverses需要注意,他的意思是动画结束时执行逆动画
        [baseLayer addAnimation:animation forKey:nil];
    }
    
    #pragma mark - 创建replicatorLayer - 5 - 三角
    - (void)creatreplicatorLayer_five
    {
        /** 子layer实现 */
        CAShapeLayer *shape = [CAShapeLayer layer];
        shape.frame = CGRectMake(0, 0, 25, 25);
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 25, 25)];
        shape.path = path.CGPath;
        shape.fillColor = [UIColor blackColor].CGColor;
    
        /** CAReplicatorLayer的实现 */
        CAReplicatorLayer *replicatorLayer = [[CAReplicatorLayer alloc] init];
        replicatorLayer.frame = CGRectMake(100, 100, 25, 25);
        replicatorLayer.backgroundColor = [UIColor cyanColor].CGColor;
        replicatorLayer.instanceDelay = 0.0;    // 动画延迟秒数
        replicatorLayer.instanceCount = 3;    // 子layer复制数
        //这里面要设置复制的layer的变换了。先向右移动一定像素,再顺时针移动一定角度。这里有个点,至少我当时不是很明白,就是为什么这么设置就会闭合成一个三角形。
    
        CATransform3D t = CATransform3DTranslate(CATransform3DIdentity, 50, 0, 0);
        t = CATransform3DRotate(t ,120 / 180.0 * M_PI, 0, 0, 1);
        replicatorLayer.instanceTransform = t;
        replicatorLayer.instanceAlphaOffset = -0.4;    // 设置透明系数是方便看复制layer的层次
        [replicatorLayer addSublayer:shape];
    //    replicatorLayer.masksToBounds = YES;
        [self.view.layer addSublayer:replicatorLayer];
        
        
        //动画1- 缩放动画
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
        animation.duration = 2;
        animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 0)];
        animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 0.2, 0)];
        animation.repeatCount = HUGE;
        animation.autoreverses = YES; //autoreverses需要注意,他的意思是动画结束时执行逆动画
        [shape addAnimation:animation forKey:nil];
        
        
        //动画2- 旋转-没做完
        CATransform3D transform = CATransform3DIdentity;
    
        CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"instanceTransform"];
        animation1.duration = 2;
    //    animation1.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1)];
    
        animation1.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1)];
        animation1.repeatCount = HUGE;
        animation1.autoreverses = YES;
        [shape addAnimation:animation1 forKey:nil];
    }
    

    CAEmitterLayer - 粒子动画

    - (void)setEmitterLayer
    {
    //创建一个CAEmitterLayer
        CAEmitterLayer *snowEmitter = [CAEmitterLayer layer];
        //指定发射源的位置   
        snowEmitter.emitterPosition = CGPointMake(self.view.bounds.size.width / 2.0, -10);
       //指定发射源的大小 
        snowEmitter.emitterSize  = CGSizeMake(self.view.bounds.size.width, 0.0);
       //指定发射源的形状 和 模式
        snowEmitter.emitterShape = kCAEmitterLayerLine;
        snowEmitter.emitterMode  = kCAEmitterLayerOutline;
        //创建CAEmitterCell
        CAEmitterCell *snowflake = [CAEmitterCell emitterCell];
        //每秒多少个
        snowflake.birthRate = 3.0;
        //存活时间
        snowflake.lifetime = 50.0;
        //初速度
        snowflake.velocity = 10;//因为动画属于落体效果,所以我们只需要设置它在 y 方向上的加速度就行了。
       //初速度范围
        snowflake.velocityRange = 5;
       //y方向的加速度
        snowflake.yAcceleration = 2;
        snowflake.emissionRange = 0;
        snowflake.contents  = (id) [[UIImage imageNamed:@"037"] CGImage];
       //缩小
        snowflake.scale = 0.5;
        snowEmitter.emitterCells = [NSArray arrayWithObject:snowflake];
    }
    

    想要手动控制粒子动画的开始和结束,我们必须通过 KVC 的方式设置 cell 的值才行。以下为代码

    开始
    CAEmitterLayer 根据自己的 emitterCells 属性找到名叫 explosion 的 cell,并设置它的 birthRate 为 500。从而间接地控制了动画的开始。

    [self.explosionLayer setValue:@500 forKeyPath:@"emitterCells.explosion.birthRate"]; 
    

    结束

    [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosion.birthRate"]; 
    

    其他比较常用的还有CATransformLayer(3D展示)、CATextLayer(文字动态展示)等

    6. 补充:iOS角度与弧度转换

    在iOS中图片的旋转单位为弧度而不是角度,所以经常会在两者之间进行转换。
    角度弧度定义
    “ 弧度”和“度”是度量角大小的两种不同的单位。
    两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度。

    参考系.png
    比如要旋转90°,就设置此参数为M_PI_2,M_PI_2是π/2。
    比如要旋转180°,就设置此参数为M_PI,M_PI是π。
    比如要旋转360°,就设置此参数为M_PI2,M_PI2是2π。
    角度转弧度
    弧度 = 角度/ 180.0 * M_PI
    #define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
    弧度转角度
    角度 = 弧度 * 180.0 / M_PI
    #define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
    
    7. 附图:
    CALayer树状结构.png CALayer属性.png 寄宿图&上下文.png CALayer子类.png Quarts 2D.png

    相关文章

      网友评论

      • Maxdon:如何在同一图层上添加文字呢

      本文标题:iOS开发经验(8)-绘图

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