0%

CoreAnimation编程指南翻译(五):构建图层树

大部分情况下, 使用图层的最优方式都是将图层与视图进行关联. 但有时也可能会使用独立图层来优化性能, 或实现一些视图难以实现的功能. 在这些情况下, 就需要知道如何创建和管理图层树.

在OS X 10.8及更高版本中, 建议只使用图层支持视图, 并设置合适的图层重绘策略.

图层树的操作

可以把一个图层嵌入到另一个图层, 这两个图层就具有了父子关系. 这种关系会影响图层的许多方面. 例如子图层的内容在父图层之上展示, 子图层的位置相对于父图层指定, 并且父图层的变换也会影响子图层.

图层的添加, 插入和删除

每个图层都有添加,插入和删除子图层的方法. Table 4-1 中是这些方法的说明.

Behavior Methods Description
Adding layers addSublayer: Adds a new sublayer object to the current layer. The sublayer is added to the end of the layer’s list of sublayers. This causes the sublayer to appear on top of any siblings with the same value in their zPosition property.
Inserting layers insertSublayer:above:,
insertSublayer:atIndex:,
insertSublayer:below:
Inserts the sublayer into the sublayer hierarchy at the specified index or at a position relative to another sublayer. When inserting above or below another sublayer, you are only specifying the sublayer’s position in the sublayers array. The actual visibility of the layers is determined primarily by the value in their zPosition property and secondarily by their position in the sublayers array.
Removing layers removeFromSuperlayer Removes the sublayer from its parent layer.
Exchanging layers replaceSublayer:with: Exchanges one sublayer for another. If the sublayer you are inserting is already in another layer hierarchy, it is removed from that hierarchy first.

上面的方法只能操作自定义图层, 与视图相关联的图层只能作为其他图层的父图层.

调整图层的位置和大小

添加或插入子图层时, 必须设置子图层的位置和大小. 也可以在子图层添加之后修改其位置和大小, 但最好养成在创建子图层时就对其位置和大小进行设置的习惯.

分别使用position属性和bounds属性对设置子图层的位置和大小.position属性与图层的锚点(anchorPoint)相关, 锚点默认在图层中心. 如果没有手动设置图层的位置和大小, Core Animation会将位置设置为(0,0), 宽高也都设置为0.

1
2
myLayer.bounds = CGRectMake(0, 0, 100, 100);
myLayer.position = CGPointMake(200, 200);

重要提示: 始终为图层的宽高设置整数值.

图层树对动画的影响

父图层的一些属性可能会对子图层的动画造成影响. speed属性就是其中之一, 该属性表示动画执行的速度. speed属性的值默认为1.0, 将其设置为2.0表示动画将以两倍的速度执行, 这样动画会在一半的时间内执行完成. speed属性的设置不仅影响当前图层, 还会影响子图层, 而且这种影响是递增的. 如果父图层和子图层都将该属性设置为2.0, 那么子图层上的动画将会以4倍的速度执行.

其他大部分父图层的属性设置对子图层的影响都是可预见的, 比如父图层的旋转变换会被应用到所有子图层, 修改父图层的不透明度也会影响子图层的不透明度. 父图层大小变化对子图层布局的影响, 参阅下节调整图层树的布局.

调整图层树的布局

Core Animation支持多种调整子图层位置和大小的方式. 在iOS中, 由于图层支持视图的广泛使用, 降低了图层树的重要性, 所以仅支持手动更新布局. 在OS X中, 还提供了其他方式来管理图层树.

只有在使用独立图层时, 图层级别的布局才有意义. 如果应用中使用图层支持视图, 请使用基于视图的布局来响应位置和大小的变化.

使用约束管理图层树(OS X)

约束使用图层与其父图层和兄弟图层之前的关系来定义其位置和大小. 定义一个约束需要以下步骤:

  1. 创建一个或多个CAConstraint对象. 使用这些对象定义约束参数.
  2. 将约束对象添加到要相应的图层上.
  3. 获取单例对象CAConstraintLayoutManager, 并赋值给父视图.

Figure 4-1 说明了可以用来定义约束的属性, 以及这些属性对图层的影响. 约束使用一种简单的规则控制图层的位置和大小.

  • Figure 4-1 约束布局管理器的属性

约束布局管理器的属性

每个约束对象封装了两个图层在同一坐标轴上的几何关系. 每个坐标轴最多包含两个约束对象, 由这两个约束对象决定哪个属性是可变的. 例如,如果指定了图层的左右边缘约束, 那么图层的大小就是可变的. 如果指定了图层的左边缘约束和宽度约束, 那么图层右边缘的位置就是可变的. 如果只指定了图层的一个边缘约束, Core Animation会创建隐式约束以保证图层大小在给定的维度中固定.

创建约束时, 必须指定以下三个信息:

  • 要约束图层的哪个方面
  • 用哪个图层作为参考
  • 与参考图层的哪个方面进行对比

Listing 4-1 中创建了一个简单约束, 将图层的垂直中点固定到其父图层的垂直中点上. 字符串superlayer代表父图层. 在创建同级图层相关的约束时, 必须使用name属性标识同级图层.

1
2
3
[myLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];

每个图层负责管理其子图层的布局. 要想在运行时使约束生效, 还需要将单例对象CAConstraintLayoutManager赋值给父图层.

Figure 4-2 展示了约束是如何工作的. 在示例中, 设计要求layerA的宽高固定, 并始终展示在父图层的中心. layerB的宽度需要与layerA相同, layerB的顶部与layerA的底部相距10像素, layerB的底部与父视图的底部也相距10像素. Listing 4-2中是具体的约束代码.

  • Figure 4-2 基于约束的布局

基于约束的布局

  • Listing 4-2 为图层创建约束
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
// Create and set a constraint layout manager for the parent layer.
theLayer.layoutManager=[CAConstraintLayoutManager layoutManager];

// Create the first sublayer.
CALayer *layerA = [CALayer layer];
layerA.name = @"layerA";
layerA.bounds = CGRectMake(0.0,0.0,100.0,25.0);
layerA.borderWidth = 2.0;

// Keep layerA centered by pinning its midpoint to its parent's midpoint.
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"superlayer"
attribute:kCAConstraintMidX]];
[theLayer addSublayer:layerA];

// Create the second sublayer
CALayer *layerB = [CALayer layer];
layerB.name = @"layerB";
layerB.borderWidth = 2.0;

// Make the width of layerB match the width of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintWidth
relativeTo:@"layerA"
attribute:kCAConstraintWidth]];

// Make the horizontal midpoint of layerB match that of layerA
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
relativeTo:@"layerA"
attribute:kCAConstraintMidX]];

// Position the top edge of layerB 10 points from the bottom edge of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
relativeTo:@"layerA"
attribute:kCAConstraintMinY
offset:-10.0]];

// Position the bottom edge of layerB 10 points
// from the bottom edge of the parent layer.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
relativeTo:@"superlayer"
attribute:kCAConstraintMinY
offset:+10.0]];

[theLayer addSublayer:layerB];

需要注意的是代码中并没有直接设置layerB的大小, 由于定义了约束, 每次更新布局时, layerB的布局都会自动更新.

⚠️警告: 不要创建循环约束, 循环约束不能计算出布局所需要的信息, 布局将会失效.

使用Autoresizing管理图层树(OS X)

Autoresizing时OS X中调整图层位置和大小的另一种方式. 使用Autoresizing, 可以指定图层边缘与其父图层相应边缘的距离是固定的还是可变的. 类似的, 可以指定图层的宽度和高度是固定的还是可变的. 使用Autoresizing定义的都是子图层与其父图层之间的关系, 不能指定兄弟图层之间的关系.

使用autoresizingMask属性设置图层的Autoresizing规则. 默认情况下, 图层被设置为固定宽度和固定高度. 布局过程中, 由Core Animation计算图层的精确位置和大小. Core Animation在使用Autoresizing规则布局之后, 会再调用delegate进行手动布局, 因此可以根据需要使用手动布局调整Autoresizing布局的结果.

手动布局

在iOS和OS X中, 可以在父图层的delegate对象上实现layoutSublayersOfLayer:方法来进行手动布局. 可以使用该方法调整任意子图层的位置和大小. 在手动布局时, 可以进行必要的计算.

图层的裁剪

与视图不同, 一个父图层不会裁剪子图层的内容, 即使子图层的内容超出了父图层的边界. 父图层默认支持子图层的完整显示. 如果需要裁剪子视图的内容, 可以设置父图层的masksToBounds属性为YES.

对图层形状的裁剪包括图层的圆角半径. Figure 4-3 中展示了masksToBounds属性对图层圆角半径的影响. 当该属性设置为NO时, 子图层能完整显示, 即便子图层的内容已经超出了父图层边界. 当该属性设置为YES时, 子图层内容被裁减.

  • Figure 4-3 子图层的裁剪

子图层的裁剪

图层之间的坐标转换

在某些情况下, 可能需要将一个图层中的某个坐标值转换为另一个图层中同一屏幕位置下的坐标. CALayer类为这种转换提供了一系列方法:

除了上述转换之外, 还可以使用convertTime:fromLayer:convertTime:toLayer:方法转换两个图层之间的时间. 每个图层都有自己的本地时间, 并且使用该时间定义与系统同步动画的开始时间和结束时间. 图层的时间默认是同步的, 但是如果修改了图层的动画速度, 则该图层的本地时间也会被修改. 可以使用转换方法处理由此引发的问题.