---页首---

Core Animation 入门系列二 设置图层对象

设置图层对象

概述

* 图层对象是Core Animation 的核心, 管理 App 的可视内容并且提供修改内容的风格和可视化的外观。iOS自动支持图层, Mac OS需要显示的启用图层才能利用它的优势。

App中启用 Core Animation 支持

* iOS 中总是启用 `Core Animation` 且 每个 view 都有一个层。而在 Mac OS 中必须通过以下几步显示的启用
	+ 链接到 `QuartzCore` 框架。(iOS应用程序只有在明确使用核心动画接口的情况下,才必须链接到这个框架。)
	+ 为一个或多个 `NSView` 对象启用图层支持要做以下几点:
		- 在 nib 文件的视图检查器为 view 启用图层,推荐将窗口的内容 view
		- 代码创建视图,调用 `setWantsLayer:` 方法并传入 `YES`,表示使用图层

更改视图关联图层对象

* 概述: 图层支持的视图默认会创建一个 CALayer 实例,但 CoreAnimation 提供了不同的图层类,提供了特殊的能力,选择一个图层类也许可以提高性能。例如 CATiledLayer 类可以高效的展示大图。

* 通过 UIView 改变图层类,大部分情况下你不用修改图层,可能在下面几种情形下你想修改
	+ 用 Metal 或 OpenGL ES 绘制内容,你可能使用 CAMetalLayer 或 CAEAGLLayer
	+ 指定的 图层提供更好的性能
	+ 你希望利用一些专门的CoreAniamtion动画层类,如粒子发射器或复制器。
 	```Objective-C
 	// Specifying the layer class of an iOS view
 	+ (Class)layerClass {
 		return [CAMetalLayer class];
 	}
	```

* 通过 NSView 改变图层类,通过重写 makeBackingLayer 方法更换默认的图层类,在这个方法的实现中,创建和返回图层对象。在一些情况下,重写这个方法可以自定义图层,如流动或平铺图层

* 在 OS X 中,图层托管让你改变图层。图层托管的视图是 NSView,可以自己创建和管理底层图层对象。例如你可以创建图层托管视图并赋值一个非默认的 CALayer 对象,也可以在需要使用单个视图来管理独立图层层次结构的情况下使用它。当你通过 setLayer: 方法为视图提供图层对象,AppKit通常采取不干预的方式。在托管模式下, AppKit 不会更新大部分图层的属性。
* 为了创建托管图层视图,你要在屏幕显示之前创建一个图层关联该视图,大概代码如下面。在设置图层对象同时,你必须调用 setWantsLayer: 方法,让视图知道要使用图层  

```Objective-C
// Create myView
// 
[myView setWantsLayer: YES];
CATiledLayer * hostedLayer = [CATiledLayer layer];
[myView setLayer: hosetedLayer];
// 
// Add myView to a view hierarchy
```Objective-C

**如果你选择自己托管图层,你必须自己设置 `contentsSacle` 属性,同时在恰当的时间要提供高分辨率内容。有关更多高分辨率内容和伸缩因子,查看 `使用高分辨率图像`(待补充)** 

* 不同的图层类提供专门的行为,CoreAnimation 定义了很多标准的图层类,每个类的设计都有一个专门的使用场景。CALayer 是所有图层类的根类,定义的行为所有的图层类都支持,图层支持的视图默认情况下使用的类型。CALayer子类及用途
	+ CAEmitterLayer 用于实现一个基于核心动画粒子发射系统。发射器图层对象控制粒子的产生及其来源。
	+ CAGradientLayer 用于绘制填充图层形状的渐变颜色(在 bounds 内,可以有任意圆角)
	+ CAMetalLayer 用于在使用 `Metal` 渲染图层内容时设置和快速绘制纹理
	+ CAEAGLLayer/CAOpenGLLayer 用于使用 OpenGL ES(iOS) 或 OpenGL (OS X)渲染图层内容时设置备份存储和上下文
	+ CAReplicatorLayer 用于自动制作一个或更多子图层的备份。复制器制作副本,使用指定属性更改副本的外观或属性
	+ CAScrollLayer 用于管理由多个子图层组成的大的可滚动区域
	+ CAShapeLayer  用于绘制三次贝赛尔曲线。绘制基于路径形状图形使用 Shape 图层是很有利的,因为它们总是生成一个清晰的路径,而不是绘制到图层后备份存储,后者在缩放时看起来不太好。清晰的结果会在主线程上呈现形状和缓存结果
	+ CATextLayer 用于渲染普通或属性字符串文本
	+ CATiledLayer 用于管理可分为较小图块的大图像,并支持放大和缩小内容,且可分别渲染
	+ CATransformLayer 用于渲染真实的3D图层层级结构,而不是由其它图层类实现的平面的图层层级
	+ QCCompositionLayer (Only OS X)用于渲染 Quartz Composition 组成

给图层提供内容

图层是数据对象,来管理应用提供的内容。图层的组成包含可视数据的位图。可以通过三种方式为位图提供内容:

- 直接给图层对象 `contents` 属性赋值一个 image 对象。(此方法是推荐的方式几乎或者很少改变)  
- 给图层设置一个代理对象,用代理绘制图层的内容。(此方法适合图层内容一段时间改变和通过外部对象提供(如 view))
- 定义一个图层的子类,重写它的一个绘制方法来自己提供内容。(自定义一个图层或改变图层的基础绘制行为)

image 为图层提供内容

- 图层只是用来管理位图图像的容器,直接给属性 contents 赋值,图层会直接使用提供的图像,不会尝试去拷贝一份,此行为在多处用到同一个图像时节约内存
- 直接赋值的类型必须是 `CGImageRef` , (在 OS X 10.6 以后,也可以是 NSImage 对象)
- 赋值时要考虑到本机分辨率的问题,例如是 Retina 屏时,需要图像的 `contentsScale` 属性适配

通过代理为图层提供内容

- 图层内容是动态,通过代理对象提供和更新内容。
	+ 如果代理实现 `displayLayer:` 此方法, 此实现负责创建位图并赋值给图层 `contents` 属性
	+ 如果代理实现 `drawLayer: inContext:` 方法,Core Animation 创建位图,创建图形上下文绘制位图,然后调用代理方法去填充位图。代理方法必须在提供的绘图上下文中绘制。
	+ 必须实现上面两个方法中的一个,如果两个都实现,则只会调用 `displayLayer: ` 方法
```Objective-C
// use delegate to provide layer's content
// overriding displayLayer: method
// the delegate use a helper object to load and display the image
// a custom property `displayYesImage`
- (void)displayLayer: (CALayer *)theLayer {
	// check the value of some state property
	if (self.displayYesImage) {
		// display the Yes image
		thePlayer.contents = [someHelperObject loadStateYesImage];
	}
	else {
		// display the No Image
		theLayer.contents = [someHelperObject loadStateNoImage];
	}
}
```
```Objective-C
// drawing a simple curved path using a fixed width and the current rendering color
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext {
    CGMutablePathRef thePath = CGPathCreateMutable();
    CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
    CGPathAddCurveToPoint(thePath,
                          NULL,
                          15.f,250.0f,
                          295.0f,250.0f,
                          295.0f,15.0f);
    CGContextBeginPath(theContext);
    CGContextAddPath(theContext, thePath);
    CGContextSetLineWidth(theContext, 5);
    CGContextStrokePath(theContext);
    // Release the path
    CFRelease(thePath);
}
```
- 如果是基于图层的视图,视图会自动使自身成为图层的代理,实现需要的代理方法,而不要改变配置,相反要重写视图的 `drawRect:` 方法来绘制你的内容。
- 在 OS X 10.8 以上,提供位图的另一种方法是重写视图 `wantsUpdateLayer` 和 `updateLayer` 方法。 重写 `wantsUpdateLayer` 且返回 YES, 让 NSView 改变一种渲染路径,从而不调用 `drawRect:` 而调用`updateLayer`,直接给图层 `contents` 属性赋值。

通过子类化给图层提供内容

- 自定义图层子类时,可以在图层的绘制方法中做任何事,图层通常是用来管理内容并不直接生成内容
- 子类化时,可以通过重写图层的 `display` 方法同时直接设置 `contents` 属性;或者重写图层的 `drawInContent:` 方法,在提供的上下文中绘制
- `display` 是更新图层内容的主入口,重写此方法表示你完全控制绘制过程,同时要负责创建CGImageRef赋值给contents属性;
- 如果仅仅只是想绘制内容或管理图层的操作,重写 `drawInContent:` 方法,让图层为你创建备份

调整图层的内容

- 给图层 `contents` 属性赋值后,图层的 `contentsGravity` 属性决定图像如何去适应当前的bounds。默认情况下,图像大于或小于当前bounds,图层拉伸去填充当前空间,如果两者的宽高比不一样则会产生变形。此时可以使用 `contentsGravity` 保证内容以最佳的方式呈现。
- `contentsGravity`赋值分为两类:(1)基于位置的重力常数,允许在不缩放图像的情况下将图像固定在bounds的边缘或角落;(2)基于比例的重力常数,允许使用几个拉伸选项来拉伸图像,有些保持宽高比,有些则不能

Position-based gravity constants of layers
Scaling-based gravity constants for layers

使用高分辨率图像

- 图层对底层设备的屏幕分辨率一无所知,仅仅是存储一个位图的指针且尽可能的以最佳的方式展示。在可 `contents` 属性赋值时,必须告诉 `Core Animation` 给 `contentsScale` 属性设置一个合适的值处理分辨率。 默认是1.0,正好是标准屏幕的分辨率。如果图像要在Retina屏上显示,请设置为2.0
- 如果直接给图层赋值位图,设置 `contentsScale` 属性值是必须的,但 UIKit和AppKit会自动去管理。

调整图层的风格和外观

图层有自己的背景和边框

- 除了的基于图像的显示外,图层还可展示填充的背景和边框,背景在图层内容的后面,边框则显示在图像的上面

![Adding a border and background to a layer

```Objective-C
// setting the background color and border of a layer
myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;
```

图层支持圆角

- 图层圆角会影响图层的背景和边框的绘制

A corner radius on a layer

图层支持内置阴影

- 可以设置图层的阴影颜色、位置,阴影的位置与系统的坐标系相一致,因此iOS和Mac OS不同
- 如果使 masksToBounds = YES 则阴影也会被裁切,如果既相用阴影又想用 bounds mask,可以使用两个图层,让包含内容的图层使用mask,然后嵌入使用阴影的图层

Applying a shadow to a layer

给 OS X 视图视觉效果添加过虑器(iOS 图层对象不能添加)

- 过虑器可以用来模糊和锐化图层内容,改变颜色、内容变形等其它操作。典型的例子是图像处理程序会用过虑器
- 对给定的图层,可以为图层内容的前景和背景过虑。通过添加 CIFilter 对象给以下属性:
	+ filters 属性包含一个过滤器的数组,只对前景内容有作用
	+ backgroundFilters 包含一个过滤数组,只对背景内容有作用
	+ compositingFilters 属性定义前景和背景内容如何合成在一起
- 给图层添加滤镜,首先必须定位和创建 CIFilter 对象,然后配置再添加到图层上。你不能通过CIFilter对象自身修改,可以使用图层的 setValue:forKeyPath:修改滤镜的值
```Objective-C
// Applying a filter to a layer
CIFilter *aFilter = [CIFilter filterWithName:@"CIPinchDistortion"];
[aFilter setValue:[NSNumber numberWithFloat:500.0] forKey:@"inputRadius"];
[aFilter setValue:[NSNumber numberWithFloat:1.25] forKey:@"inputScale"];
[aFilter setValue:[CIVector vectorWithX:250.0 Y:150.0] forKey:@"inputCenter"];
myLayer.filters = [NSArray arrayWithObject:aFilter]
```

为图层添加自定义属性

- CAAnimation和CALayer类扩展了键值编码约定来支持自定义属性。您可以使用此行为向图层添加数据,并使用您定义的自定义键检索数据。您甚至可以将操作与自定义属性关联起来,以便在更改属性时执行相应的动画。
posted @ 2020-01-13 09:20  20190311  阅读(450)  评论(0编辑  收藏  举报
---页脚---