# CAScrollLayer
对于一个未转换的图层,它的`bounds`和它的`frame`是一样的,`frame`属性是由`bounds`属性自动计算而出的,所以更改任意一个值都会更新其他值。
但是如果你只想显示一个大图层里面的一小部分呢。比如说,你可能有一个很大的图片,你希望用户能够随意滑动,或者是一个数据或文本的长列表。在一个典型的iOS应用中,你可能会用到`UITableView`或是`UIScrollView`,但是对于独立的图层来说,什么会等价于刚刚提到的`UITableView`和`UIScrollView`呢?
在第二章中,我们探索了图层的`contentsRect`属性的用法,它的确是能够解决在图层中小地方显示大图片的解决方法。但是如果你的图层包含子图层那它就不是一个非常好的解决方案,因为,这样做的话每次你想『滑动』可视区域的时候,你就需要手工重新计算并更新所有的子图层位置。
这个时候就需要`CAScrollLayer`了。`CAScrollLayer`有一个`-scrollToPoint:`方法,它自动适应`bounds`的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。前面提到过,Core Animation并不处理用户输入,所以`CAScrollLayer`并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。
让我们来用`CAScrollLayer`来常见一个基本的`UIScrollView`替代品。我们将会用`CAScrollLayer`作为视图的宿主图层,并创建一个自定义的`UIView`,然后用`UIPanGestureRecognizer`实现触摸事件响应。这段代码见清单6.10\. 图6.11是运行效果:`ScrollView`显示了一个大于它的`frame`的`UIImageView`。
清单6.10 用`CAScrollLayer`实现滑动视图
~~~
#import "ScrollView.h"
#import @implementation ScrollView
+ (Class)layerClass
{
return [CAScrollLayer class];
}
- (void)setUp
{
//enable clipping
self.layer.masksToBounds = YES;
//attach pan gesture recognizer
UIPanGestureRecognizer *recognizer = nil;
recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:recognizer];
}
- (id)initWithFrame:(CGRect)frame
{
//this is called when view is created in code
if ((self = [super initWithFrame:frame])) {
[self setUp];
}
return self;
}
- (void)awakeFromNib {
//this is called when view is created from a nib
[self setUp];
}
- (void)pan:(UIPanGestureRecognizer *)recognizer
{
//get the offset by subtracting the pan gesture
//translation from the current bounds origin
CGPoint offset = self.bounds.origin;
offset.x -= [recognizer translationInView:self].x;
offset.y -= [recognizer translationInView:self].y;
//scroll the layer
[(CAScrollLayer *)self.layer scrollToPoint:offset];
//reset the pan gesture translation
[recognizer setTranslation:CGPointZero inView:self];
}
@end
~~~
图6.11 用`UIScrollView`创建一个凑合的滑动视图
不同于`UIScrollView`,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。图层内容极有可能滑出视图的边界并无限滑下去。`CAScrollLayer`并没有等同于`UIScrollView`中`contentSize`的属性,所以当`CAScrollLayer`滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。
那你一定会奇怪用`CAScrollLayer`的意义到底何在,因为你可以简单地用一个普通的`CALayer`然后手动适应边界原点啊。真相其实并不复杂,`UIScrollView`并没有用`CAScrollLayer`,事实上,就是简单的通过直接操作图层边界来实现滑动。
`CAScrollLayer`有一个潜在的有用特性。如果你查看`CAScrollLayer`的头文件,你就会注意到有一个扩展分类实现了一些方法和属性:
~~~
- (void)scrollPoint:(CGPoint)p;
- (void)scrollRectToVisible:(CGRect)r;
@property(readonly) CGRect visibleRect;
~~~
看到这些方法和属性名,你也许会以为这些方法给每个`CALayer`实例增加了滑动功能。但是事实上他们只是放置在`CAScrollLayer`中的图层的实用方法。`scrollPoint:`方法从图层树中查找并找到第一个可用的`CAScrollLayer`,然后滑动它使得指定点成为可视的。`scrollRectToVisible:`方法实现了同样的事情只不过是作用在一个矩形上的。`visibleRect`属性决定图层(如果存在的话)的哪部分是当前的可视区域。如果你自己实现这些方法就会相对容易明白一点,但是`CAScrollLayer`帮你省了这些麻烦,所以当涉及到实现图层滑动的时候就可以用上了。
- Introduction
- 1. 图层树
- 1.1 图层与视图
- 1.2 图层的能力
- 1.3 使用图层
- 1.4 总结
- 2. 寄宿图
- 2.1 contents属性
- 2.2 Custom Drawing
- 2.3 总结
- 3. 图层几何学
- 3.1 布局
- 3.2 锚点
- 3.3 坐标系
- 3.4 Hit Testing
- 3.5 自动布局
- 3.6 总结
- 4. 视觉效果
- 4.1 圆角
- 4.2 图层边框
- 4.3 阴影
- 4.4 图层蒙板
- 4.5 拉伸过滤
- 4.6 组透明
- 4.7 总结
- 5. 变换
- 5.1 仿射变换
- 5.2 3D变换
- 5.3 固体对象
- 5.4 总结
- 6. 专用图层
- 6.1 CAShapeLayer
- 6.2 CATextLayer
- 6.3 CATransformLayer
- 6.4 CAGradientLayer
- 6.5 CAReplicatorLayer
- 6.6 CAScrollLayer
- 6.7 CATiledLayer
- 6.8 CAEmitterLayer
- 6.9 CAEAGLLayer
- 6.10 AVPlayerLayer
- 6.11 总结
- 7. 隐式动画
- 7.1 事务
- 7.2 完成块
- 7.3 图层行为
- 7.4 呈现与模型
- 7.5 总结
- 8. 显式动画
- 8.1 属性动画
- 8.2 动画组
- 8.3 过渡
- 8.4 在动画过程中取消动画
- 8.5 总结
- 9. 图层时间
- 9.1 CAMediaTiming协议
- 9.2 层级关系时间
- 9.3 手动动画
- 9.4 总结
- 10. 缓冲
- 10.1 动画速度
- 10.2 自定义缓冲函数
- 10.3 总结
- 11. 基于定时器的动画
- 11.1 定时帧
- 11.2 物理模拟
- 12. 性能调优
- 12.1. CPU VS GPU
- 12.2 测量,而不是猜测
- 12.3 Instruments
- 12.4 总结
- 13. 高效绘图
- 13.1 软件绘图
- 13.2 矢量图形
- 13.3 脏矩形
- 13.4 异步绘制
- 13.5 总结
- 14. 图像IO
- 14.1 加载和潜伏
- 14.2 缓存
- 14.3 文件格式
- 14.4 总结
- 15. 图层性能
- 15.1 隐式绘制
- 15.2 离屏渲染
- 15.3 混合和过度绘制
- 15.4 减少图层数量
- 15.5 总结