[iOS Animation]-CALayer 图层几何学二

坐标系

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

这样对于放置图层会更加方便,由于你能够经过移动根图层来将它的子图层做为一个总体来移动,可是有时候你须要知道一个图层的绝对位置,或者是相对于另外一个图层的位置,而不是它当前父图层的位置。 github

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

1
2
3
4
- (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;

这些方法能够把定义在一个图层坐标系下的点或者矩形转换成另外一个图层坐标系下的点或者矩形 ide

翻转的几何结构

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

Z坐标轴

和UIView严格的二维坐标系不一样,CALayer存在于一个三维空间当中。除了咱们已经讨论过的position和anchorPoint属性以外,CALayer还有另外两个属性,zPosition和anchorPointZ,两者都是在Z轴上描述图层位置的浮点类型。 工具

注意这里并无更的属性来描述由宽和高作成的bounds了,图层是一个彻底扁平的对象,你能够把它们想象成相似于一页二维的坚硬的纸片,用胶水粘成一个空洞,就像三维结构的折纸同样。 布局

zPosition属性在大多数状况下其实并不经常使用。在第五章,咱们将会涉及CATransform3D,你会知道如何在三维空间移动和旋转图层,除了作变换以外,zPosition最实用的功能就是改变图层的显示顺序了。 测试

一般,图层是根据它们子图层的sublayers出现的顺序来类绘制的,这就是所谓的画家的算法--就像一个画家在墙上做画--后被绘制上的图层将会遮盖住以前的图层,可是经过增长图层的zPosition,就能够把图层向相机方向前置,因而它就在全部其余图层的前面了(或者至少是小于它的zPosition值的图层的前面)。 ui

这里所谓的“相机”其实是相对于用户是视角,这里和iPhone背后的内置相机没任何关系。 atom

图3.8显示了在Interface Builder内的一对视图,正如你所见,首先出如今视图层级绿色的视图被绘制在红色视图的后面。

图3.8

图3.8 在视图层级中绿色视图被绘制在红色视图的后面

咱们但愿在真实的应用中也能显示出绘图的顺序,一样地,若是咱们提升绿色视图的zPosition(清单3.3),咱们会发现顺序就反了(图3.9)。其实并不须要增长太多,视图都很是地薄,因此给zPosition提升一个像素就可让绿色视图前置,固然0.1或者0.0001也可以作到,可是最好不要这样,由于浮点类型四舍五入的计算可能会形成一些不便的麻烦。

清单3.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface ViewController ()
 
@property (nonatomic, weak) IBOutlet UIView *greenView;
@property (nonatomic, weak) IBOutlet UIView *redView;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //move the green view zPosition nearer to the camera
    self.greenView.layer.zPosition = 1.0f;
}
@end

图3.9

图3.9 绿色视图被绘制在红色视图的前面

Hit Testing

第一章“图层树”证明了最好使用图层相关视图,而不是建立独立的图层关系。其中一个缘由就是要处理额外复杂的触摸事件。

CALayer并不关心任何响应链事件,因此不能直接处理触摸事件或者手势。可是它有一系列的方法帮你处理事件:-containsPoint:和-hitTest:。

-containsPoint:接受一个在本图层坐标系下的CGPoint,若是这个点在图层frame范围内就返回YES。如清单3.4所示第一章的项目的另外一个合适的版本,也就是使用-containsPoint:方法来判断究竟是白色仍是蓝色的图层被触摸了 (图3.10)。这须要把触摸坐标转换成每一个图层坐标系下的坐标,结果很不方便。

清单3.4 使用containsPoint判断被点击的图层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@interface ViewController ()
 
@property (nonatomic, weak) IBOutlet UIView *layerView;
@property (nonatomic, weak) CALayer *blueLayer;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create sublayer
    self.blueLayer = [CALayer layer];
    self.blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [self.layerView.layer addSublayer:self.blueLayer];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //get touch position relative to main view
    CGPoint point = [[touches anyObject] locationInView:self.view];
    //convert point to the white layer's coordinates
    point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
    //get layer using containsPoint:
    if ([self.layerView.layer containsPoint:point]) {
        //convert point to blueLayer’s coordinates
        point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
        if ([self.blueLayer containsPoint:point]) {
            [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
                                        message:nil
                                       delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
        } else {
            [[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
                                        message:nil
                                       delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
        }
    }
}
 
@end

图3.10

图3.10 点击图层被正确标识

-hitTest:方法一样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层自己,或者包含这个坐标点的叶子节点图层。这意味着再也不须要像使用-containsPoint:那样,人工地在每一个子图层变换或者测试点击的坐标。若是这个点在最外面图层的范围以外,则返回nil。具体使用-hitTest:方法被点击图层的代码如清单3.5所示。

清单3.5 使用hitTest判断被点击的图层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //get touch position
    CGPoint point = [[touches anyObject] locationInView:self.view];
    //get touched layer
    CALayer *layer = [self.layerView.layer hitTest:point];
    //get layer using hitTest
    if (layer == self.blueLayer) {
        [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
                                    message:nil
                                   delegate:nil
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
    } else if (layer == self.layerView.layer) {
        [[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
                                    message:nil
                                   delegate:nil
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
    }
}

注意当调用图层的-hitTest:方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件相似)。以前提到的zPosition属性能够明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。

这意味着若是改变了图层的z轴顺序,你会发现将不可以检测到最前方的视图点击事件,这是由于被另外一个图层遮盖住了,虽然它的zPosition值较小,可是在图层树中的顺序靠前。咱们将在第五章详细讨论这个问题。

自动布局

你可能用过UIViewAutoresizingMask类型的一些常量,应用于当父视图改变尺寸的时候,相应UIView的frame也跟着更新的场景(一般用于横竖屏切换)。

在iOS6中,苹果介绍了自动排版机制,它和自动调整不一样,而且更加复杂。

在Mac OS平台,CALayer有一个叫作layoutManager的属性能够经过CALayoutManager协议和CAConstraintLayoutManager类来实现自动排版的机制。但因为某些缘由,这在iOS上并不适用。

当使用视图的时候,能够充分利用UIView类接口暴露出来的UIViewAutoresizingMask和NSLayoutConstraintAPI,但若是想随意控制CALayer的布局,就须要手工操做。最简单的方法就是使用CALayerDelegate以下函数:

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

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

这也是为何最好使用视图而不是单独的图层来构建应用程序的另外一个重要缘由之一。

总结

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

相关文章
相关标签/搜索