UIView & CALayer关系

原文链接:https://z251257144.github.io/ios/UI/UIView_CALayer.html

UIViewCALayer概念上很相似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容,管理子图层的位置。两者不同的是:UIView可以处理触摸事件;CALayer不处理用户的交互,不参与响应事件传递。

每一个UIView都有一个CALayer实例的图层属性,我们也可以通过UIViewlayer属性访问这个图层。视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树中执行相同的操作。

对于UIView视图来说真正的负责内容展示的其实是它内部的CALayerUIView只是将自身的展示任务交给了内部的CALayer完成,而它还肩负着一些其它的任务,比如说用户的交互响应,提供一些Core Animation底层方法的高级接口等。

思考:为什么提供提供基于UIView和CALayer两个平行的层级关系呢?
答:原因在于要做职责分离,避免重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触摸的用户界面和基于鼠标键盘有着本质的区别。iOS系统中我们使用的是UIKitUIView,而在MacOS系统使用的是AppKitNSView,所以在这种情况下将展示部分使用CALayer分离出来会给苹果的多平台系统开发带来便捷。

定义

UIView定义

@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate>

@property(class, nonatomic, readonly) Class layerClass; // default is [CALayer class]. Used when creating the underlying layer for the view.
@property(nonatomic,readonly,strong) CALayer  *layer; // returns view's layer. Will always return a non-nil value. view is layer's delegate

@end

CALayer定义

@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>

UIResponder定义

@interface UIResponder : NSObject <UIResponderStandardEditActions>

由类定义可以看出:CALayer是继承于NSObjectUIView是继承于UIResponderUIResponder继承于NSObject。结构如下:

image

在UIView的定义中可以看到layer的描述,每个UIView都拥有一个非空的layer,默认是CALayer类型; ViewLayer的代理,代理协议是CALayerDelegateUIView内部实现CALayerDelegate方法。

CALayerDelegate定义

@protocol CALayerDelegate <NSObject>
@optional

/* If defined, called by the default implementation of the -display
 * method, in which case it should implement the entire display
 * process (typically by setting the `contents' property). */

- (void)displayLayer:(CALayer *)layer;

/* If defined, called by the default implementation of -drawInContext: */

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

/* If defined, called by the default implementation of the -display method.
 * Allows the delegate to configure any layer state affecting contents prior
 * to -drawLayer:InContext: such as `contentsFormat' and `opaque'. It will not
 * be called if the delegate implements -displayLayer. */

- (void)layerWillDraw:(CALayer *)layer
  API_AVAILABLE(macos(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/* Called by the default -layoutSublayers implementation before the layout
 * manager is checked. Note that if the delegate method is invoked, the
 * layout manager will be ignored. */

- (void)layoutSublayersOfLayer:(CALayer *)layer;

/* If defined, called by the default implementation of the
 * -actionForKey: method. Should return an object implementing the
 * CAAction protocol. May return 'nil' if the delegate doesn't specify
 * a behavior for the current event. Returning the null object (i.e.
 * '[NSNull null]') explicitly forces no further search. (I.e. the
 * +defaultActionForKey: method will not be called.) */

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

@end

布局关系

一个CALayer的布局是由它的anchorPoint, position, boundstransform共同决定的,而UIViewframe只是简单的返回其layerframe,同样UIViewcenterbounds也是返回 其Layer的一些属性。

CALayer有两种坐标系类型:"基于点的坐标系"和"基于单位的坐标系"

1、基于点的坐标系: 指定直接映射到屏幕坐标的值或必须相对于另一个图层指定的值时使用基于点的坐标,例如图层的position属性。基于点的坐标系相关的属性:bounds、position、frame。

2、基于单位的坐标系: 当值不应与屏幕坐标相关联时使用单位坐标,因为它与某个其他值相关。例如,图层的anchorPoint属性指定相对于图层本身边界的点,该点可以更改。

frame:当前layer相当于 superLayer的坐标系,frame.origin是相对于superLayer左上角的位置,frame.size就是当前layer的大小。这个和UIView的frame是类似的。

bounds:当前layer相对于自身的坐标系,bounds.size 和 frame.size是一样的。bounds.origin是相对于自身的左上角的位置,bounds.origin = (0,0)。

position:是当前layer上的一个点,相对于superLayer左上角的位置。

anchorPoint:锚点都是对于自身来讲的,用来确定位置,该值是相对bounds.size的比例值来确定的,默认值为(0.5,0.5),例如(0,0), (1,1)分别表示左上角、右下角。

四者之间的关系:

framebounds之间的关系很简单,frame.origin = CGMakePoint(0,0)就是bounds。

CALayer中决定Layer大小和在superLayer中位置的是boundsposition。尽管Layer具有frame属性,但该属性实际上是从boundsposition属性中的值派生的,并且使用频率较低。

anchorPointpositionframe之间的相对关系.

Ⅰ、当确定锚点时,改变frame时, position的值为:

position.x= frame.origin.x+ anchorPoint.x* bounds.size.width;
position.y= frame.origin.y+ anchorPoint.y* bounds.size.height;

锚点不变时,frame改变,position会随着变化。

Ⅱ、当确定锚点时,改变position时, frame的值为:

frame.origin.x= position.x- anchorPoint.x* bounds.size.width;
frame.origin.y= position.y- anchorPoint.y* bounds.size.height;

锚点不变时,position变化,frame会随着变化。

Ⅲ、改变锚点(anchorPoint), frame的值变化为:

frame.origin.x= - anchorPoint.x* bounds.size.width+ position.x;
frame.origin.y= - anchorPoint.y* bounds.size.height+ position.y;

锚点改变, position不影响,frame会随着变化。

为什么第三条关系中的锚点改变了,变化的是frame,而不是 position呢?

因为position是真正相对于superLayer的位置。而frame.origin只是通过position+anchorPoint * bounds.size.height关系被动推到出来的,当改变anchorPoint的值时,实际改变的是frame的origin,由此可以看出position是实际位置的控制点,当锚点变化时,position的值是固定不变的。动态变化的是frame的origin。

image

属性转换公式

UIView中frame = CALayer中frame = CALayer中origin, bounds.size

CALayer中origin = position-(anchorPoint*bounds.size)/2

UIView中bounds = CALayer中bounds

UIView中center = CALayer中position+bounds.size * anchorPoint

隐式动画

每个view都有一个layer,但是也有一些不依附view单独存在的layer,如CAShapelayer。它们不需要附加到 view上就可以在屏幕上显示内容。

基本上你改变一个单独的layer的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的隐式动画)。然而,如果你改变的是viewlayer 的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有 layer,但是当 layer 附加在 view 上时,它的默认的隐式动画的 layer 行为就不起作用了。

在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:

UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们。

是因为任何可动画的 layer 属性改变时,layer 都会寻找并运行合适的action来实行这个改变。在 Core Animation 的专业术语中就把这样的动画统称为动作 (action,或者 CAAction)。

layer 通过向它的delegate发送actionForLayer:forKey:消息来询问提供一个对应属性变化的actiondelegate 可以通过返回以下三者之一来进行响应:

1、它可以返回一个动作对象,这种情况下 layer 将使用这个动作。

2、它可以返回一个 nil, 这样 layer 就会到其他地方继续寻找。

3、它可以返回一个 NSNull 对象,告诉layer这里不需要执行一个动作,搜索也会就此停止。

layer在背后支持一个view的时候,view就是它的 delegate。

总结

UIView:属于UIKit.framework框架,负责渲染矩形区域的内容,为矩形区域添加动画,响应区域的触摸事件、布局和管理一个或者多个子视图

CALAyer:属于QuartzCore.framework,是用来绘制内容的,对内容进行动画处理,不能处理用户事件。

每个UIView内部都有一个CALayer在背后提供内容的绘制和显示,并且UIView的尺寸样式都由内部的Layer所提供,LayerView多了个AnchorPoint。两者都是树状层级结构,layer内部有SubLayersView 内部有 SubViews

View显示的时候,UIView 做为 LayerCALayerDelegate,View的显示内容取决于内部的 CALayerdisplay

CALayer是默认修改属性支持隐式动画的,在给UIViewLayer做动画的时候,View作为Layer的代理,Layer通过actionForLayer:forKey:View请求相应的action(动画行为)。

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

参考文档:

https://www.jianshu.com/p/ed40da9303b1

https://www.jianshu.com/p/c6924e2ab232

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350