iOS动画编程——CoreAnimation编程指南(三)

Home / iOS MrLee 2015-10-22 2545

1.1 什么是图层树的层次结构

图层树是核心动画里面类似Cocoa视图的层次结构。比如一个NSView或者UIView的实例拥有父视图(superview)和子视图(subview),一个核心动画的图层拥有父图层(suplayer)和子图层(sublayer)。图层树和视图结构一样提供了很多便利:
  • 复杂的接口可以由简单的图层来组合,避免了硕大和复杂的继承化子类。图层非常合适于这种堆叠方式来合成复杂的功能。
  • 每个图层定义了一个基于其父图层的坐标系的坐标系。当一个图层变换的时候,它的子图层同样变换。
  • 一个动态的图层树,可以在程序运行的时候重新设置。图层可以创建并添加为一个图层的第一个子图层,然后从其他图层的图层树上面删除。

1.2 在视图里面显示图层

核心动画不提供在一个窗口(window)实际显示图层的手段,它们必须通过视图来托管。当视图和图层一起的时候,视图为图层提供了底层的事件处理,而图层为视图提供了显示的内容。
iOS上面的视图系统直接建立在核心动画的图层上面。每个UIView的实例会自动的创建一个CALayer类的实例,然后把该实例赋值给视图的layer属性。你可以在需要的时候向视图的图层里面添加子图层。
在Mac OS X,您必须配置一个NSView的实例,通过这样一种方式才可以让它托管图层。为了显示图层树的根图层,你可以设置一个视图的图层和配置视图以便使用图层:

代码 1  向view中插入layer


// theView is an existing view in a window
// theRootLayer is the root layer of a layer tree
 
[theView setLayer: theRootLayer];
[theView setWantsLayer:YES];

 

1.3 从图层结构里面添加和删除图层

简单的实例化一个图层并不意味已经把它插入了一个图层树。而是通过以下表1的方法来实现从图层树里面添加、插入、替换和删除图层。

表 1  图层树的管理函数


Method

Result

addSublayer:

Appends the layer to the receiver’s sublayers array.

insertSublayer:atIndex:

Inserts the layer as a sublayer of the receiver at the specified index.

insertSublayer:below:

Inserts the layer into the receiver’s sublayers array, below the specified sublayer.

insertSublayer:above:

Inserts the layer into the receiver’s sublayers array, above the specified sublayer.

removeFromSuperlayer

Removes the receiver from the sublayers array or mask property of the receiver’s superlayer.

replaceSublayer:with:

Replaces the layer in the receiver’s sublayers array with the specified new layer.

 
你也可以通过使用一个图层的数组来设置图层的子图层,甚至可以扩展设置父图层的sublayers属性。当把图层的sublayers属性设置了一个图层的数组值的时候,你必须保证数组里面每个图层的父图层已经被设置为nil。
默认情况下从一个可视化图层树里面插入和删除图层将会触发动画。当把一个图层添加为子图层的时候,将会触发父图层返回标识符为kCAOnOrderIn动画。当从图层的子图层里面删除一个图层的时候,将会触发父图层返回一个标识符为kCAOnOrderOut的动画。当替换图层的子图层里面的一个图层的时候,将会触发父图层返回一个标识符为KCATransition的动画。当你操作图层树的时候,你可以禁用动画或者改变使用任何标识符的动画。
 

1.4 图层的位置调整和大小改变

图层创建以后,你可以通过改变图层的几何属性:frame、bounds、position、anchorPoint和zPosition来编程式移动和改变图层大小。
如果一个图层的属性needsDisplayOnBoundsChange被设置为YES的时候,当图层的bounds属性改变的时候,图层的内容将会被重新缓存起来。默认情况下图层的needsDisplayOnBoundsChange属性值为NO。
默认情况下,设置图层的属性frame、bounds、position、anchorPoint和zPosition属性将会导致图层动画显示新值。
 

1.4.1 自动调整图层大小

CALayer提供了一个机制,在父图层被移动或者改变大小的时候,子图层可以自动的跟着移动和调整大小。在很多情况下简单的配置一个图层的自动调整掩码(autoresizing  mask)可以适当的适应程序的行为。
一个图层的自动调整掩码可以通过指定CAAutoresizingMask的常量结合或运算(OR)所得的结果赋值给图层的autoresizingMask属性值。表2列举了掩码常量和这些掩码如何影响图层的大小调整行为。

表 2  自动缩放掩码及其解释


Autoresizing Mask

Description

kCALayerHeightSizable

如果设置了,则layer的高度按比例随父layer的高度变化。

kCALayerWidthSizable

如果设置了,则layer的宽度按比例随父layer的宽度变化。

kCALayerMinXMargin

如果设置了,则layer的左边距按比例随父layer的宽度变化。如果未设置,则layer的左边距保持原来相对父layer的位置。

kCALayerMaxXMargin

如果设置了,则layer的右边距按比例随父layer的宽度变化。如果未设置,则layer的右边距保持原来相对父layer的位置。

kCALayerMaxYMargin

如果设置了,则layer的上边距按比例随父layer的宽度变化。如果未设置,则layer的上边距保持原来相对父layer的位置。

kCALayerMinYMargin

如果设置了,则layer的下边距按比例随父layer的宽度变化。如果未设置,则layer的下边距保持原来相对父layer的位置。

 
例如,为了把保持图层位于它父图层的相对左下角位置,你可以使用kCALayerMaxXMargin | kCALayerMaxYMargin。当沿着一个轴具有多个方向被设置为适应可变的时候,那么调整大小的尺寸为使其均匀分布的值。图1提供了一个常量值的位置的图形表示。

图 1  Layer的自缩放掩码常量

当这些常量里面的任何一个被省略的时候,图层的布局在这个方向上值是固定的。当一个常量包含在图层的自动调整掩码里面的时候,该方向上的图层的布局值是适应可变的。
CALayer的子类可以重写函数resizeSublayersWithOldSize:和resizeWithOldSuperlayerSize:来定制化的自动调整图层大小的行为。图层的函数resizeSublayersWithOldSize:将会在bounds属性被修改的时候自动的触发执行,同时发送一个消息resizeWithOldSuperlayerSize:给图层的每个子图层。图层的每个子图层根据自动调整掩码的属性来比较就的边界值和新的边界值来调整它的位置和大小。
 

1.5 裁剪子图层

在Cocoa的视图里面,当子视图超出父视图的边界的时候,视图将会被裁剪以适应父视图的大小。图层去掉了这个限制,允许子层全部显示,无论自己相对于父层位置如何。图层的masksToBounds属性决定了是否子图层是否相对父图层裁剪。该属性masksToBounds的默认值为NO,即防止子图层被相对于父图层裁剪。表2显示了当设置图层的masksToBounds属性导致的结果,和它如何影响layerB和layerC的显示。

图 2  masksToBounds 属性的实例值

 


图层内容

当我们使用Cocoa的视图的时候,我们必须继承NSView或者UIView并且重载函数drawRect:来显示任何内容。但是CALayer实例可以直接使用,而无需继承子类。因为CALayer是一个键-值编码兼容的容器类,你可以在实例里面存储任意值,所以子类实例化完全可以避免。

1.1 给CALayer提供内容

你可以通过以下任何一种方法指定CALayer实例的内容:
  • 使用包含图片内容的CGImageRef来显式的设置图层的contents的属性。
  • 指定一个委托,它提供或者重绘内容。
  • 继承CALayer类重载显示的函数。

1.1.1 设置contents属性

图层的图片内容可以通过指定contents属性的值为CGImageRef。当图层被创建的时候或者在任何其他时候,这个操作可以在其他实体上面完成(如表3所示)。

代码 1  设定layer的contents属性


CALayer *theLayer;
 
// create the layer and set the bounds and position
theLayer=[CALayer layer];
theLayer.position=CGPointMake(50.0f,50.0f);
theLayer.bounds=CGRectMake(0.0f,0.0f,100.0f,100.0f);
 
// set the contents property to a CGImageRef
// specified by theImage (loaded elsewhere)
theLayer.contents=theImage;

 

1.1.2 通过委托提供内容

你可以绘制图层的内容,或更好的封装图层的内容图片,通过创建一个委托类实现下列方法之一:
displayLayer:或drawLayer:inContext:
实现委托重绘的方法并不意味会自动的触发图层使用实现的方法来重绘内容。而是你要显式的告诉一个图层实例来重新缓存内容,通过发送以下任何一个方法setNeedsDisplay或者setNeedsDisplayInRect:的消息,或者把图层的needsDisplayOnBoundsChange属性值设置为YES。
通过委托实现方法displayLayer:可以根据特定的图层决定显示什么图片,还可以更加需要设置图层的contents属性值。下面的例子是“图层的坐标系”部分的,它实现displayerLayer:方法根据state的值设置theLayer的contents属性。子类不需要存储state的值,因为CALayer的实例是一个键-值编码容器。

代码 2  委托方法displayLayer:的实现示例


- (void)displayLayer:(CALayer *)theLayer
{
    // check the value of the layer's state key
    if ([[theLayer valueForKey:@"state"] boolValue])
    {
        // display the yes image
        theLayer.contents=[someHelperObject loadStateYesImage];
    }
    else {
        // display the no image
        theLayer.contents=[someHelperObject loadStateNoImage];
    }
}

 
如果你必须重绘图层的内容,而不是通过加载图片,那你需要实现drawLayer:inContext:方法。通过委托可以决定哪些内容是需要的并使用CGContextRef来重绘内容。
下面的例子是“指定图层的几何”部分内容,它实现了drawLayer:inContext:方法使用lineWidth键值来重绘一个路径(path),返回therLayer。
 

代码 3  代理方法drawLayer:inContext:的实现示例


- (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,
                          [[theLayer valueForKey:@"lineWidth"] floatValue]);
    CGContextStrokePath(theContext);
 
    // release the path
    CFRelease(thePath);
}

 

1.1.3 通过子类提供图层的内容

虽然通常情况不需要这样做,但是你仍可以继承CALayer直接重载重绘和显示方法。这个通常发生在你的图层需要定制行为而委托又无法满足需求的时候。
子类可以重载CALayer的显示方法,设置图层的内容为适当的图片。下面的例子是“变换图层的几何”部分的内容,它提供了和“图层的坐标系”例子相同的功能。不同的是子类定义state为实例的属性,而不是根据CALayer的键-值编码容器获取。

 

代码 4  CALayer display 方法的覆盖示例


- (void)display
{
    // check the value of the layer's state key
    if (self.state)
    {
        // display the yes image
        self.contents=[someHelperObject loadStateYesImage];
    }
    else {
        // display the no image
        self.contents=[someHelperObject loadStateNoImage];
    }
}

 
CALayer子类可以通过重载drawInContext:绘制图层的内容到一个图形上下文。下面的例子是“修改变换的数据结构”的内容,它和“指定图层的几何”里面实现委托的办法一样产生相同的图片内容。唯一的不同的是实现委托里面的lineWidth和lineColor现在是子类实例的属性。

Listing 5  覆盖layer的drawInContext:方法示例


- (void)drawInContext:(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,
                          self.lineWidth);
    CGContextSetStrokeColorWithColor(theContext,
                                     self.lineColor);
    CGContextStrokePath(theContext);
    CFRelease(thePath);
}

 
继承CALayer并且实现其中的重绘方法并不意味重绘会自动发生。你必须显式的促使实例重新缓存其内容,可以通过发送以下任何一个方法setNeedsDisplay或setNeedsDisplayInRect:的消息,亦或者设置图层的needsDisplaOnBoundsChange属性为YES。
 

1.2 修改图层内容的位置

CALayer的属性contentsGravity允许你在图层的边界内容修改图层的contents图片的位置或者伸缩值。默认情况下,内容的图像完全填充层的边界,忽视自然的图像宽高比。
使用contentsGravity位置常量,你可以指定图片位于图层任何一个边界,比如位于图层的角落,或者图层边界的中心。然而当你使用位置常量的时候,contentsCenter属性会被忽略。表1列举了位置常量和他们相应的位置。
 
表 1  layer的contentsGravity属性的定位常量
Position constant Description
kCAGravityTopLeft Positions the content image in the top left corner of the layer.
kCAGravityTop Positions the content image horizontally centered along the top edge of the layer.
kCAGravityTopRight Positions the content image in the top right corner of the layer.
kCAGravityLeft Positions the content image vertically centered on the left edge of the layer.
kCAGravityCenter Positions the content image at the center of the layer.
kCAGravityRight Positions the content image vertically centered on the right edge of the layer.
kCAGravityBottomLeft Positions the content image in the bottom left corner of the layer.
kCAGravityBottom Positions the content image centered along the bottom edge of the layer.
kCAGravityBottomRight Positions the content image in the top right corner of the layer.
 
“图层的坐标系”标识了所支持的内容位置和他们相应的常量。

图 1  layer的contentsGravity属性的定位常量

    通过设置contentsGravity属性为其他一个常量(如表2所示)。图层的内容图片可以被向上或者向下拉伸, 仅当使用其他任何一个调整大小的常量的时候,contentsCenter属性才会对内容图片起作用。
 

表 2  Layer的 contentsGravity 属性的缩放常量


Scaling constant Description
kCAGravityResize Resize the content image to completely fill the layer bounds, potentially ignoring the natural aspect of the content. This is the default.
kCAGravityResizeAspect Resize the content image to scale such that it is displayed as large as possible within the layer bounds, yet still retains its natural aspect.
kCAGravityResizeAspectFill Resize the content image to scale such that it is displayed filling the layer bounds, yet retaining its natural aspect. This may cause the content to extend outside the layer bounds.
“变换图层的几何”演示了如何使用调整大小的模式来调整一个正方形图像的大小让其适应图层的方形边界。

图 2  Layer的 contentsGravity 属性的缩放常量

 

 
注意:使用任何常量kCAGravityResize、kCAGravityResizeAspect和kCAGravityResizeAspectFill和表1中的重心位置常量无关。图层的内容将会填充整个边界,所以使用这些常量无法改变图层内容的位置。

本文链接:https://www.it72.com/6511.htm

推荐阅读
最新回复 (0)
返回