iOS --- UIView与CALayer的联系与区别

UIView是iOS系统中界面元素的基础, 所有的界面元素都继承自它, UIView本身完全是由CoreAnimation来实现. 真正的绘图部分, 是由一个CALayer类来管理. UIView更像是一个CALayer的管理器, 所以访问它的与绘图和坐标相关的属性, 如frame, bounds等, 实际上都是在访问其所包含的CALayer的相关属性. 因此, 可以在所有UIView的子类上实现动画效果.
UIView继承自UIResponder, 能接收并响应事件, 负责显示内容的管理, 而CALayer继承自NSObject, 不能响应事件, 负责显示内容的绘制.

UIView的layer属性

获取UIView的layer属性:

CALayer *layer = self.view.layer;

UIView的layerClass方法返回layer使用的类. 可以重写该方法, 使UIView的继承类使用指定的CALayer来显示, 如下代码可使其使用OpenGL来进行绘制.

+ (Class)layerClass {
     return [CAEAGLLayer class];
}

对于UIViewController, 可做如下操作让其UIView使用OpenGL来绘制. CALayer的层级结构与UIView的类似, addSublayer与addSubview的作用类似.

CAEAGLLayer *eaglLayer = [CAEAGLLayer layer];
eaglLayer.frame = self.view.frame;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
[self.view.layer addSublayer:eaglLayer];

当CALayer有了更新, 而不能立即显示, 可使用setNeedsDisplay方法来重绘显示.

[myLayer setNeedsDisplay];
// 更新局部区域
[myLayer setNeedsDisplayInRect:CGRectMake(100,100,50,50)];
// CoreGraphics可以直接使用renderInContext
[myLayer renderInContext:UIGraphicsGetCurrent()];

CALayer

一个UIView可以有多个CALayer, UIView的尺寸样式都是由内部的CALayer来提供的. 每一个CALayer显示一种效果, 因此, 通过添加多种效果的CALayer, 以增强UIView的显示能力. 如UIView自身不能设置圆角等效果, 而CALayer可设置边框, 圆角, 阴影和变换变形等. 两者都有树状层级结构, CALayer有subLayers, UIView有subViews.

contents属性

CALayer *aLayer = [[CALayer alloc] init;
aLayer.contents = [[UIImage imageNamed:@"testImage"] CGImage];
aLayer.contentsGravity = kCAGravityResizeAspectFill;

这里使用图片给contents赋值的话, 一定要是CGImage.
可以通过设置contentsGravity设置其显示模式, 相当于UIView的contentMode, 如kCAGravityResizeAspectFill 是铺满的 。kCAGravityResizeAspect 是显示自己本身的大小 。
若果图片超出CALayer 可以使用maskToBounds 进行裁剪 ,剪掉超出的部分 (配合圆角使用不错)。

contentsRect

用来裁剪图片, 默认的contentsRect是{0, 0, 1, 1}, 即整个图都默认可见. 如果我们改成{0,0,0.5,0.5} 图像就就会被裁剪掉左上角的1/4.

CALayer效果

aLayer.backgroundColor = [[[UIColor redColor] colorWithAlphaComponent:0.2] CGColor];
// 边框
aLayer.boardColor = [[UIColor blueColor] CGColor];
aLayer.boardWidth = 2.0;
// 圆角
aLayer.cornerRadius = 10.0;
// 阴影
aLayer.shadowColor = [[UIColor greenColor] CGColor];
aLayer.shadowOpacity = 0.5;
aLayer.shadowOffset = CGSizeMake(2, 1);

[self.view.layer addSublayer:aLayer];

圆角(cornerRadius)和阴影(shadowColor), 二者不能同时出现, 所以可以通过两个重叠的UIView, 分别使其CALayer显示圆角和阴影.

变换

QuartzCore的CATransform3D提供了旋转,缩放和倾斜等变换效果.
添加3D或者仿射变换如下:

myView.layer.transform = CATransform3DMakeScale(-1.0, -1.0, 1.0);
CGAffineTransform transform = CGAffineTransformMakeRotation(45.0);
myView.layer.affineTransform = transform;

取消动画可以使用[layer removeAllAnimations];
可参考swift详解之二十四—————CoreAnimation(一)CALayer.

Layer Tree

CALayer内部维护着三份layer tree, 分别是presentLayer Tree(动画树), mode Layer Tree(模型树), Render Tree(渲染树), 在做iOS动画的时候, 我们修改动画的属性, 在动画的其实是Layer的presentLayer的属性值, 而最终展示在界面上其实是提供UIView的modeLayer.

UIView与CALayer的区别

UIView继承自UIResponder,主要特点是可以响应触摸事件。而CALayer是实际的图层内容管理, 不会直接渲染到屏幕上。大家干的的事情不一样,是两个东西,大家的存在互不影响,理所当然。

事件响应

简单将CALayer视作只能显示, 不能响应事件的特殊UIView; 或将UIView视作能接收和响应事件的CALayer.
UIKit使用UIResponder作为响应对象, 来响应系统传递过来的事件并进行处理.
UIApplication, UIViewController, UIView和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类. UIResponder中定义了处理各种事件和事件传递的接口.处理事件如touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:等.
CALayer直接继承自NSObject, 并没有相应的处理事件的接口.
关于UIResponder的更多内容请参考以下两篇文章:
1. Responder Chain简析
2. 视图和窗口架构

frame, position, bounds调用

一个CALayer的frame是由其anchorPoint, position, bounds, transform共同决定的, 而一个UIView的的frame只是简单地返回CALayer的frame, 同样UIView的center和bounds也只是简单返回CALayer的Position和Bounds对应属性.

机制与策略分离

UIView主要是对显示内容的管理, 而CALayer主要是显示内容的绘制. UIView是CALayer的CALayerDelegate, 在代理方法内部[UIView(CALayerDelegate) drawLayer:inContext]调用UIView的drawRect方法, 从而绘制出UIView的内容. UIView的显示内容由内部的CALayer:display方法来实现.
编程问题都可以抽离出机制和策略部分。机制一旦实现,就会很少更改,但策略会经常得到优化。CALayer也可以看做是一种机制,提供图层绘制,CALayer的头文件基本上是没怎么变过的,而UIView可以看做是策略,变动很多。越是底层越是机制,越是机制就越是稳定。机制与策略分离,可以使得需要修改的代码更少,特别是底层代码,这样可以提高系统的稳定性。UIView遮蔽了大部分的CALayer接口,抽取构造出更易用的frame和动画实现,这样上手更容易。

CALayer默认产生隐式动画

CALayer默认修改属性支持隐式动画. 对于每一个 UIView 都有一个 layer,把这个 layer 且称作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s. 即在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。
在给UIView的CALayer做动画的时候, UIView作为CALayer的代理, CALayer通过actionForLayer:forKey:向UIView请求相应的动画action.
在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:

UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们
是因为任何可动画的 layer 属性改变时,layer 都会寻找并运行合适的 ‘action’ 来实行这个改变。在 Core Animation 的专业术语中就把这样的动画统称为动作 (action,或者 CAAction)。
layer 通过向它的 delegate 发送 actionForLayer:forKey: 消息来询问提供一个对应属性变化的 action。delegate 可以通过返回以下三者之一来进行响应:
它可以返回一个动作对象,这种情况下 layer 将使用这个动作。
它可以返回一个 nil, 这样 layer 就会到其他地方继续寻找。
它可以返回一个 NSNull 对象,告诉 layer 这里不需要执行一个动作,搜索也会就此停止。
当 layer 在背后支持一个 view 的时候,view 就是它的 delegate;
这部分的具体内容参考:http://objccn.io/issue-12-4/重点内容

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页