阅读 572

iOS核心动画高级技巧——图层几何学

图层几何学

在这一章中,我们将要看一看图层内部是如何根据父图层和兄弟图层来控制位置和尺寸的。另外我们也会涉及如何管理图层的集合结构,以及它是如何被自动调整和自动布局影响的。

布局

UIView有三个比较重要的布局属性:frameboundscenterCALayer对应地叫做frameboundsposition

frame代表了图层的外部坐标(也就是在父图层上占据的空间),bounds是内部坐标({0, 0}通常是图层的左上角),centerposition都代表了相对父图层anchorPoint所在的位置。

记住当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽高可能和bounds的宽高不再一致了。

锚点

视图的center属性和图层的position属性都指定了anchorPoint相对于父图层的位置。图层的anchorPoint通过position来控制它的frame的位置,你可以认为anchorPoint是用来移动图层的把柄

默认来说,anchorPoint位于图层的中点,所以图层将会以这个点为中心放置。anchorPoint属性并没有被UIView接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的anchorPoint可以被移动,比如你可以把它置于图层frame的左上角,于是图层的内容将会向右下角的position方向移动,而不是居中。

和之前的contentsRectcontentsCenter属性类似,anchorPoint单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。anchorPoint可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。

原书通过一个模拟闹钟的项目来举例说明了在什么场合需要改变anchorPoint。原本钟表的图片在围绕着中心旋转,可以通过给每个指针的achorPoint做一些平移,获得一个期待的支点。

坐标系

和视图一样,图层在图层树当中也是相对于父图层按层级关系放置,一个图层的position依赖于它父图层的bounds,如果父图层发生了移动,它的所有子图层也会跟着移动。

这样对于放置图层会更加方便,因为你可以通过移动根图层来将它的子图层作为一个整体来移动,但是有时候你需要知道一个图层的绝对位置,或者是相对于另一个图层的位置,而不是它当前父图层的位置。

CALayer给不同坐标系之间的图层转换提供了一些工具类方法:

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
复制代码

这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形。

翻转的几何结构

常规来说,在iOS上,一个图层的position位于父图层的左上角,但是在Mac OS上,通常是位于左上角。Core Animation可以通过geometryFlipped属性来适配这两种情况,它决定了一个图层的坐标是否相对于父图层垂直翻转,是一个BOOL类型。在iOS上通过设置它为YES意味着它的子图层将会被垂直翻转,也就是将会沿着底部排版而不是通常的顶部(它的所有子图层也同理,除非把它们的geometryFlipped属性也设为YES)。

Z坐标轴

UIView严格的二维坐标系不同,CALayer存在于一个三维空间当中。除了我们已经讨论过的positionanchorPoint属性之外,CALayer还有另外两个属性,zPositionanchorPointZ,二者都是在Z轴上描述图层位置的浮点类型。

通常,图层是根据它们子图层的subLayers出现的顺序来绘制的,这就是所谓的画家算法——就想一个画家在墙上作画——后被绘制上的图层将会遮盖住之前的图层,但是通过增加图层的zPosition,就可以把图层向相机方向前置,于是它就在所有其他图层的前面了(或者至少是小于它的zPosition值的图层的前面)。

Hit Testing

CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:containsPointhitTest

containsPoint接受一个在本图层坐标下的CGPoint,如果这个点在图层frame范围内就返回YES

hitTest方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用containsPoint那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。

注意当调用图层的hitTest方法时,测算的顺序严格依赖于图层当中的图层顺序(和UIView处理事件类似)。之前提到的zPosition属性可以明显改变屏幕上图层的顺序,但不能改变触摸事件被处理的顺序。

这意味着如果改变了图层的z轴顺序,你会发现将不能检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的zPosition值较小,但是在图层树中的顺序靠前。

自动布局

当使用视图的时候,可以充分利用UIView类接口暴露出来的UIViewAutoresizingMaskNSLayoutConstraintAPI,但如果想随意控制CALayer的布局,就需要手工操作。最简单的方法就是使用CALayerDelegate如下函数:

-(void)layoutSublayersOfLayer:(CALayer*)layer;
复制代码

当图层的bounds发生改变,或者图层的setNeedsLayout方法被调用的时候,这个函数将会被执行。这使得你可以手动地重新摆放或者重新调整子视图的大小,但是不能像UIViewautoresizingMaskconstraints属性做到自适应屏幕旋转。

这也是为什么最好使用视图而不是单独的图层来构建应用程序的另一个重要原因之一。

总结

本章涉及了CALayer的几何结构,包括它的framepositionbounds,介绍了三维空间内图层的概念,以及如何在独立的图层内响应事件,最后简单说明了在iOS平台中,Core Animation对自动调整和自动布局支持的缺乏。

文章分类
iOS
文章标签