CALayer

CALayer通过四个属性来确定大小和位置, 分别为:frame、bounds、position、anchorPoint。

frame:与view中的frame概念相同,subLayer左上角相对于supLayer坐标系的位置关系;width, height表示subLayer的宽度和高度。

bounds:与view中的bounds概念相同,subLayer左上角相对于自身坐标系的关系;width, height表示subLayer的宽度和高度。

anchorpoint:代表该layer哪个点相对父layer的位置;默认锚点为0.5,0.5

postion:代表该layer在父layer的位置,和anchorpoint配合使用


假如:

现在我们需要把红色图层添加到一个绿色图层中,假设红色图层的position取值(100, 100)

1,如果我们将锚点取值为(0, 0),那么显示的效果如下图所示:

2,如果我们将锚点取值为(0.5, 0.5),那么显示的效果如下图所示:

示例:

UIView *testV = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];

    testV.backgroundColor = [UIColor redColor];

    [self.view addSubview:testV];

//方式一:只设置frame

    CALayer*layer = [[CALayer alloc]init];

    layer.backgroundColor = [UIColor blackColor].CGColor;

    layer.frame=CGRectMake(10,10,50,50);

 NSLog(@"%@",NSStringFromCGPoint(layer.anchorPoint)); //默认为(0.5,0.5)

    NSLog(@"%@",NSStringFromCGPoint(layer.position)); // layer的锚点相对于父layer左上角的位置(35, 35)

NSLog(@"%@",NSStringFromCGRect(layer.bounds)); //{{0, 0}, {50, 50}}Layer左上角相对于自身坐标系的关系,会影响layer的子layer的位置

    [testV.layer addSublayer:layer];

方式一

方式二:设置postion和anchorpoint和bounds确定位置

 CALayer*layer = [[CALayeralloc]init];

    layer.backgroundColor = [UIColor blackColor].CGColor;

    layer.anchorPoint=CGPointMake(0,0);

    layer.position=CGPointMake(10,10);

    layer.bounds=CGRectMake(0,0,50,50); //主要为了设置size

    [testV.layer addSublayer:layer];

 NSLog(@"%@",NSStringFromCGRect(layer.frame));  //输出{{10, 10}, {50, 50}}

方式二


加入我们在方式一的基础上修改这些属性呢?会发生什么情况?

  //假如修改layer的frame:

     layer.frame=CGRectMake(20,20,60,60);

    NSLog(@"%@",NSStringFromCGPoint(layer.position)); // postion改变为(50,50)

    NSLog(@"%@",NSStringFromCGPoint(layer.anchorPoint)); //(0.5,0.5)不变

    //假如修改layer的position

    layer.position=CGPointMake(40,40); //x坐标变为40-宽50/2=15

    NSLog(@"%@",NSStringFromCGRect(layer.frame)); //变为((15, 15), (50, 50))

    NSLog(@"%@",NSStringFromCGPoint(layer.anchorPoint)); //(0.5,0.5)不变

//假如修改layer的bounds

     layer.frame=CGRectMake(10,10,50,50);

    layer.bounds=CGRectMake(10,10,50,50); //修改bounds不会影响自己的位置但是会影响子layer的位置,10,10代表本地坐标系向坐上分别移动10个位置

     CALayer*sublayer = [[CALayer alloc]init];

    sublayer.frame=CGRectMake(0,0,20,20);

    sublayer.backgroundColor = [UIColor yellowColor].CGColor;

    [layer addSublayer:sublayer];


bounds

改变transform:anchorPoint和position不变,

其它知识

一:我们来看一种layer的层次结构Layer Tree,这种层次结构分为以下三种:

Model Tree :也就是我们通常所说的layer。

Presentation Tree:呈现出来的layer,也就是我们做动画时你看到的那个layer,可以通过layer.presentationLayer获得。

Render Tree :私有,无法访问。主要是对Presentation Tree数据进行渲染,并且不会阻塞线程。

二:CALayer支持继承,支持添加Sublayer,支持对sublayer进行层次调整

常用的CALayer子类:

CAEmitterLayer    发射器层,用来控制粒子效果

CAGradientLayer    梯度层,颜色渐变

CAEAGLayer    用OpenGL ES绘制的层

CAReplicationLayer    用来自动复制sublayer

CAScrollLayer    用来管理可滑动的区域

CAShapeLayer    绘制立体的贝塞尔曲线

CATextLayer    可以绘制AttributeString

CATiledLayer    用来管理一副可以被分割的大图

CATransformLayer    用来渲染3D layer的层次结构

三:绘制CALayer的三种方式

1)把一个图像对象直接赋值给contents属性(这是提供CALayer内容的最好方式)

imageLayer.contents = (id)[UIImage imageNamed:@"lichen.jpg"].CGImage;

2)设置delegate,让代理绘制layer的内容

当layer中的内容是需要动态改变的时候,可以使用delegate来实现

两个代理方法:

displayLayer:如果代理实现了这个方法,那么要绘制一个bitmap,然后赋值给contents属性

drawLayer:inContext:如果代理实现了这个方法,Core Animation提供一个context来生成bitmap,你所做的只是把想要的内容绘制到context

注意:代理必须至少实现两个代理方法其中的一个,如果都实现,则调用displayLayer:

 CALayer*layer = [[CALayeralloc]init];

layer.delegate = self;

 [layer setNeedsDisplay];

- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx{

    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 30, 30));

    CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);

   CGContextFillPath(ctx);

}

3)继承CALayer,重写绘制方法,来提供layer的内容,必须调用setNeedsDisplay

-(void)drawInContext:(CGContextRef)ctx{

    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 30, 30));

    CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);

    CGContextFillPath(ctx);

}

四:Layer Tree


layer trees

图层树共有三种类型:ModleTree、PresentationTree、RenderTree;ModelTree是我们直接用CAlayer创建的或者使用view.layer获得的,ModelTree背后还存在两份图层树的拷贝,一个是呈现树(Presentation Tree),一个是渲染树(Render Tree). 呈现树可以通过,PresentationTree是呈现树它的属性值和动画运行过程中界面上看到的是一致的,RenderTree渲染树是对呈现树的数据进行渲染,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,所以你会发现Animation的动画并不会阻塞主线程.

例子:

 @property(nonatomic, strong)CALayer *myLayer;

 @property(nonatomic, strong)NSTimer *tiemr;

     CALayer*layer = [CALayerlayer];

    self.myLayer= layer;

    layer.frame=CGRectMake(100,100,50,50);

    layer.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layeraddSublayer:layer];

- (void)tiemrrrr{

    NSLog(@"%f",self.myLayer.modelLayer.opacity);

}

//点击执行动画并打印modelLayer的值

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event{

     NSLog(@"%f",self.myLayer.modelLayer.opacity);

    // 开启事务

    [CATransaction begin];

    // 设置动画时长

    [CATransaction setAnimationDuration:5.0];

    self.myLayer.opacity=0.0;

    [CATransaction commit];


    self.tiemr = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(tiemrrrr) userInfo:nil repeats:YES];

}

输出:1.000000

0.000000

 0.000000

从上面可以看出动画执行过程中layer的属性从源值瞬间变为目标值

如果将NSLog(@"%f",self.myLayer.modelLayer.opacity);改为 NSLog(@"%f",self.myLayer.presentationLayer.opacity);

 1.000000

 0.989950

0.975799

0.956881

。。。。

可以看出presentationLayer是一系列的变化值

五:CALayer conent属性:

CALayer有一个与你所要展现的效果的bitmap相结合的contents属性,

@property(nullable, strong) id contents;

有三种方式创建content:

1.当layer的content基本不变时可以直接把image对象直接赋值给content

2.当layer的content可能周期改变或者通过外部提供时,可以把layer的delegate赋值给外部对象,让delegate来绘制content

3.当你想创建一个sublayer或者改变layer的绘制行为的时候,通过重写sublayer的绘制方法来给layer的content赋值。

直接赋值image给content

layer只是管理bitmap图片的一个容器,所以可以直接把image(必须是CGImageRef)直接赋值给content。layer不会将你的image复制,而是直接使用你提供的image。在多处使用同一张图片的时候可以节省内存。

The image you assign to a layer must be a CGImageRef type.

这里要注意的是,contents虽然是id类型,但是如果你赋值的不是CGImageRef类型的值,会得到空白的图层。更头疼的是UIImage有一个CGImage属性,它返回的是一个CGImageRef类型值,但是你

aLayer.contents = [UIImageimageWithName(@"pic")].CGImage

的时候发现,编译器报错了,因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型,要通过bridged关键之转换(编译器会提示你这样做):

aLayer.contents = (__brideid)[UIImageimageWithName(@"pic")].CGImage

CALayer和UIView的区别:

1.首先 View可以接受并处理事件,而 Layer 不可以

UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。UIApplication、UIViewController、UIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。

在 UIResponder中定义了处理各种事件和事件传递的接口, 而 CALayer直接继承 NSObject,并没有相应的处理事件的接口。

2.View和CALayer的Frame映射及View如何创建CALayer.

一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。(PS:center有些特列)为了证明这些,我做了如下的测试。

3.UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制。

当UIView需要显示到屏幕上时,会调用DrawRect:方法进行绘图,并且将所有的内容绘制在自己的图层上Property()CALayer *layer,绘图完成后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示UIView 的Layer属性在系统内部,被维护着三份拷贝。分别是逻辑树,这里是代码可以操作的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上的内容

UIView 本身更像是一个CALayer的管理器,UIView 有个属性CALayer *layer ,所有从UIView继承的对象都继承了该属性。因此,可以通过layer 属性对view 进行 转换、缩放、旋转等操作 

4.在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。

DrawRect和layer


drawRect调用机制

从上可以看出UIView是UILayer的代理,UIView实现了drawLayer方法,然后调用drawRect方法进行绘制

drawRect调用机制:

drawRect调用是在Controller->loadView,,Controller->viewDidLoad 两方法之后调用的。所以不用担心在控制器中,这些View的drawRect就开始画了。这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).

1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。

2、该方法在调用sizeThatFits后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。

3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。

4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0. 

以上1,2推荐;而3,4不提倡

隐式动画:

先来一个小demo:

self.colorLayer = [CALayer layer];

self.colorLayer.frame=CGRectMake(0,0,100,100);

self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;

 CATransition *transition = [CATransition animation];

transition.type = kCATransitionPush;

 transition.subtype = kCATransitionFromLeft;

self.colorLayer.actions = @{@"backgroundColor":transition}; //为backgroundColor提供了一个action,当修改backgroundColor时会触发action,隐式动画就是为属性提供了一个默认的action。

    [self.tview.layer addSublayer:self.colorLayer];

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event{

    self.colorLayer.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:0.0 alpha:1.0].CGColor;

}

下面是隐式动画的具体过程:

1.图层首先检测自己有没有委托,并且委托实现了actionForLayer: forKey:方法,如果有则直接返回

2.如果没有委托或者委托没有实现actionForLayer: forKey:方法,就分别查找actions字典和styles字典

3.如果都没有则调用默认action

如何禁用隐式动画?

1.直接使用CATransacition的setDisableAction:方法

2.UIView默认禁用calayer动画

针对第二种:

  testV *testv = [[testV alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];

    self.tview= testv;

    testv.backgroundColor = [UIColor redColor];

    [self.view addSubview:testv];

    NSLog(@"outside:%@",[testv actionForLayer:testv.layer forKey:@"backgroundColor"]); //输出nil

    [UIView beginAnimations:nil context:nil];

    NSLog(@"Inside:%@",[testv actionForLayer:testv.layer forKey:@"backgroundColor"]); //输出

    [UIView commitAnimations];

上面表明当属性在UIView动画块之外时,UIView的actionForLayer方法直接返回nil来禁用隐式动画,而在动画块之内时返回一个非空对象。

UIView的绘制流程:

UIView绘制流程

当我们调用[UIView setNeedsDisplay]方法时,并没有执行立即执行绘制工作。

而是马上调用[view.layer setNeedsDisplay]方法,给当前layer打上脏标记。

在当前RunLoop快要结束的时候调用layer 的display方法,来进入到当前视图的真正绘制当中。

在layer的display方法内部,系统会判断layer的layer.delegate是否实现了displayLayer:方法,a.如果没有实现,则执行系统的绘制流程;b.如果实现了则会进入异步绘制的入口

最后把绘制完的backing store(可以理解为位图)提交给GPU。

系统绘制流程:


系统绘制流程

对流程加以说明:

在layer内部会创建一个backing store,我们可以理解为CGContextRef上下文。

判断layer是否有delegate:

2.1 如果有delegate,则会执行[layer.delegate drawLayer:inContext](这个方法的执行是在系统内部执行的),然后在这个方法中会调用view的drawRect:方法,也就是我们重写view的drawRect:方法才会被调用到。

2.2 如果没有delegate,会调用layer的drawInContext方法,也就是我们可以重写的layer的该方法,此刻会被调用到。

最后把绘制完的backing store(可以理解为位图)提交给GPU。

异步绘制流程:


异步绘制

我们在某一个时机调用了setNeedsdispay方法

系统会在runloop将要结束的时候调用[CAlayer display]方法

如果我们的代理实现了dispayLayer这个方法,会调用dispayLayer这个方法。我们可以去子线程里面进行异步绘制,主线程可以做其他工作

子线程里面:

1)创建上下文、2)UI控件的绘制工作、3)代理负责生产对应的bitmap,回到主线程,设置bitmap作为layer.contents属性的值,把绘制的视图显示在layer上面

你可能感兴趣的:(CALayer)