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];
改变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
图层树共有三种类型: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
从上可以看出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 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上面