8 Explicit Animations 指明的动画 笔记


Explicit Animations 指明的动画 笔记

 

 

If you want something done right, do it yourself.

如果你想让事情做好,那就自动来。

 

Charles-Guillaume Etienne

The previous chapter introduced the concept of implicit animations. Implicit animations are a straightforward way to create animated user interfaces on iOS, and they are the mechanism on which UIKit's own animation methods are based, but they are not a completely general-purpose animation solution. In this chapter, we will look at explicit animations, which allow us to specify custom animations for particular properties or create nonlinear animations, such as a movement along an arbitrary curve.

Property Animations

 

The first type of explicit animation we will look at is the property animation. Property animations target a single property of a layer and specify a target value or range of values for that property to animate between. Property animations come in two flavors: basic and keyframe.

属性动画分为两种:basic keyframe

Basic Animations

 

An animation is a change that happens over time, and the simplest form of change is when one value changes to another, which is exactly what CABasicAnimation is designed to model.

动画就是在时间上的改变,最简单的改变形式是当一个值改向另外一个,这就是CABasicAnimation设计的。

 

CABasicAnimation is a concrete subclass of the abstract CAPropertyAnimation class, which in turn is a subclass of CAAnimation, the abstract base class for all animation types supported by Core Animation.

CABasicAnimation CAPropertyAnimation类的具体类,而CAPropertyAnimation又是CAAnimation的子类。

As an abstract class, CAAnimation doesn't actually do very much on its own. It provides a timing function (as explained in Chapter 10, "Easing"), a  delegate (used to get feedback about the animation state), and a removedOnCompletion flag, used to indicate whether the animation should automatically be released after it has finished (this defaults to YES, which prevents your application's memory footprint from spiraling out of control).

作为一个抽象类,CAAnimation 其实并没有做什么。它提供了一个时间函数 一个代理 (用来回调动画的状态)一个removedOnCompletion 标签,用来指示动画是否自动释放在它完成后。

CAAnimation also implements a number of protocols, including CAAction (allowing any CAAnimation subclass to be supplied as a layer action) and CAMediaTiming (which is explained in detail in Chapter 9, "Layer Time").

还提供了一些协议,包括CAAction CAMediaTiming

 

CAPropertyAnimation acts upon a single property, specified by the animation's keyPath value.

 CAPropertyAnimation根据一个属性 的值动作。

A CAAnimation is always applied to a specific CALayer, so the keyPath is relative to that layer.

CAAnimation 总是提供一个指定的CALayer,所以keypath与这个layer关联。 

The fact that this is a key path (a sequence of dot-delimited keys that can point to an arbitrarily nested object within a hierarchy) rather than just a property name is interesting because it means that animations can be applied not only to properties of the layer itself, but to properties of its member objects, and even virtual properties (more on this later).

事实情况是key path 不仅仅是一个属性名字,也就意味着动画不仅仅可以用到layer 的属性,而且可以是它的成员对象,甚至virtual properties .

CABasicAnimation extends CAPropertyAnimation with three additional attributes:

 

id fromValue

id toValue

id byValue

 

These are fairly self-explanatory: fromValue represents the value of the property at the start of the animation; toValue represents its value at the end of the animation; byValue represents the relative amount by which the value changes during the animation.

这些都是字解释的: fromeValue 代表动画开始的值,toValue 代表动画结束的值。byValue代表相对数在动画过程中,值的变化。

 

By combining these three attributes, you can specify a value change in various different ways. The type is defined as id (as opposed to something more specific) because property animations can be used with a number of different property types, including numeric values, vectors, transform matrices, and even colors and images.

A property of type id can contain any NSObject derivative, but often you will want to animate property types that do not actually inherit from NSObject, which means that you will need to either wrap the value in an object (known as boxing) or cast it to an object (known as toll-free bridging), which is possible for certain Core Foundation types that behave like Objective-C classes, even though they aren't. It's not always obvious how to convert the expected data type to an id-compatible value, but the common cases are listed in Table 8.1.

Table 8.1 Boxing Primitive Values for Use in a CAPropertyAnimation

 

Type Object Type    

 

 

 

The fromValue, toValue, and byValue properties can be used in various combinations, but you should not specify all three at once because that could result in a contradiction.

fromValue ,toValue , byValue 可以有很多组合,但不能同时用这三个。

For example, if you were to specify a fromValue of 2, a toValue of 4, and a byValue of 3, Core Animation would not know whether the final value should be 4 (as specified by toValue) or 5 (fromValue + byValue). The exact rules around how these properties can be used are neatly documented in the CABasicAnimation header file, so we won't repeat them here. In general, you will only need to specify either the toValue or byValue; the other values can be determined automatically from context.

Let's try an example: We'll modify our color fade animation from Chapter 7, "Implicit Animations," to use an explicit CABasicAnimation instead of an implicit one. Listing 8.1 shows the code.

 

Listing 8.1 Setting the Layer Background Color with CABasicAnimation

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIView *layerView;

@property (nonatomic, strong) IBOutlet CALayer *colorLayer;

 

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//create sublayer

self.colorLayer = [CALayer layer];
self.colorLayer.frame
 = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;

//add it to our view

 

[self.layerView.layer addSublayer:self.colorLayer];

}

IBAction)changeColor {

 

 

//create a new random color

 

CGFloat red = arc4random() / (CGFloat)INT_MAX;

CGFloat green = arc4random() / (CGFloat)INT_MAX;

CGFloat blue = arc4random() / (CGFloat)INT_MAX;

UIColor *color = [UIColor colorWithRed:red

green:green blue:blue alpha:1.0];

//create a basic animation

 

CABasicAnimation *animation = [CABasicAnimation animation];

animation.keyPath = @"backgroundColor";

animation.toValue = (__bridge id)color.CGColor;

//apply animation to layer

[self.colorLayer addAnimation:animation forKey:nil]; }

@end

When we run the example, it doesn't work as expected. Tapping the button causes the layer to animate to a new color, but it then immediately snaps back to its original value.

 

The reason for this is that animations do not modify the layer's model, only its presentation (see Chapter 7).

这不工作,因为animation 并不修改layermodel,仅仅是它的presentation. 

Once the animation finishes and is removed from the layer, the layer reverts back to the appearance defined by its model properties. We never changed the backgroundColor property, so the layer returns to its original color.

When we were using implicit animation before, the underlying action was implemented using a CABasicAnimation exactly like the one we have just used. (You might recall that in Chapter 7, we logged the result of the -actionForLayer:forKey: delegate method and saw that the action type was a CABasicAnimation.) However, in that case, we triggered the animation by setting the property. Now we are performing the same animation directly, but we aren't setting the property any more (hence the snap-back problem).

 

Assigning our animation as a layer action (and then simply triggering the animation by changing the property value) is by far the easiest approach to keeping property values and animation states in sync, but assuming that we cannot do that for some reason (usually because the layer we need to animate is a UIView backing layer), we have two choices for when we can update the property value: immediately before the animation starts or immediately after it finishes.

我们有两个选择更新属性值:在动画开始后和在它解说之后。

 

Updating the property before the animation has started is the simpler of those options, but it means that we cannot take advantage of the implicit fromValue, so we will need to manually set the fromValue in our animation to match the current value in the layer.

Taking that into account, if we insert the following two lines between where we create our animation and where we add it to the layer, it should get rid of the snap-back:

animation.fromValue = (__bridge id)self.colorLayer.backgroundColor; self.colorLayer.backgroundColor = color.CGColor;

 

That works, but is potentially unreliable. We should really derive the fromValue from the presentation layer (if it exists) rather than the model layer, in case there is already an animation in progress.

这样做有效,但是不可靠。我们实际上是从presentation layer获取的fromValue而不是model layer. 

Also, because the layer in this case is not a backing layer, we should disable implicit animations using a CATransaction before setting the property, or the default layer action may interfere with our explicit animation. (In practice, the explicit animation always seems to override the implicit one, but this behavior is not documented, so it's better to be safe than sorry.)

If we make those changes, we end up with the following:

 

CALayer *layer = self.colorLayer.presentationLayer ?: self.colorLayer; animation.fromValue = (__bridge id)layer.backgroundColor;

[CATransaction begin];

[CATransaction setDisableActions:YES];

self.colorLayer.backgroundColor = color.CGColor;

[CATransaction commit];

That's quite a lot of code to have to add to each and every animation. Fortunately, we can derive this information automatically from the CABasicAnimation object itself, so we can create a reusable method. Listing 8.2 shows a modified version of our first example that includes a method for applying a CABasicAnimation without needing to repeat this boilerplate code each time.

 

Listing 8.2 A Reusable Method for Fixing Animation Snap-Back

- (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer

 

{

 

//set the from value (using presentation layer if available)

animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath];

 

//update the property in advance

//note: this approach will only work if toValue != nil

[CATransaction begin];

[CATransaction setDisableActions:YES];

[layer setValue:animation.toValue forKeyPath:animation.keyPath];

[CATransaction commit];

//apply animation to layer

[layer addAnimation:animation forKey:nil];

}

- (IBAction)changeColor {

//create a new random color

 

CGFloat red = arc4random() / (CGFloat)INT_MAX;

CGFloat green = arc4random() / (CGFloat)INT_MAX;

CGFloat blue = arc4random() / (CGFloat)INT_MAX;

UIColor *color = [UIColor colorWithRed:red  green:green blue:blue alpha:1.0];

//create a basic animation

 

CABasicAnimation *animation = [CABasicAnimation animation];

animation.keyPath = @"backgroundColor";

animation.toValue = (__bridge id)color.CGColor;

//apply animation without snap-back

 

[self applyBasicAnimation:animation toLayer:self.colorLayer];

}

This simple implementation only handles animations with a toValue, not a byValue, but it's a good start toward a general solution. You could package it up as a category method on CALayer to make it more convenient and reusable.

 

This might all seem like a lot of trouble to solve such a seemingly simple problem, but the alternative is considerably more complex. If we don't update the target property before we begin the animation, we cannot update it until after the animation has fully completed or we will cancel the CABasicAnimation in progress. That means we need to update the property at the exact point when the animation has finished, but before it gets removed from the layer and the property snaps back to its original value.

也就是说我们需要在动画完成的是hi偶,但在它从layer移走之前,属性没有到原来的值的时候要更新属性。

How can we determine that point?

CAAnimationDelegate

 

When using implicit animations in Chapter 7, we were able to detect when an animation finished by using the CATransaction completion block. That approach isn't available when using explicit animations, however, because the animation isn't associated with a transaction.

CATransaction不行了,因为用明确的动画不和transaction关联。

 

To find out when an explicit animation has finished, we need to make use of the animation's delegate property, which is an object conforming to the CAAnimationDelegate protocol.

我们需要利用动画的delegate属性,一个对象遵从了CAAnimationDelegate协议。

 

CAAnimationDelegate is an ad hoc protocol, so you won't find a CAAnimationDelegate @protocol defined in any header file, but you can find the supported methods in the CAAnimation header or in Apple's developer documentation. In this case, we use the -animationDidStop:finished: method to update our layer's backgroundColor immediately after the animation has finished.

我们可以使用-animationDidStop:finished:方法更layer's  backgroundColor在动画完成后。

 

We need to set up a new transaction and disable layer actions when we update the property; otherwise, the animation will occur twice—once due to our explicit CABasicAnimation, and then again afterward due to the implicit animation action for that property. See Listing 8.3 for the complete implementation.

我们需要设置一个新的transaction 并不允许layer actions 当我们更新属性。否则的话,动画将出现两次。

 

Listing 8.3 Fixing the Background Color Value Once Animation Completes

@implementation ViewController

  • - (void)viewDidLoad {

    [super viewDidLoad];

    //create sublayer

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame
     = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;

    //add it to our view

    [self.layerView.layer addSublayer:self.colorLayer];

  • }
  • -(IBAction)changeColor {

    //create a new random color

    CGFloat red = arc4random() / (CGFloat)INT_MAX;

  • CGFloat green = arc4random() / (CGFloat)INT_MAX;
  • CGFloat blue = arc4random() / (CGFloat)INT_MAX; UIColor *color = [UIColor colorWithRed:red

    green:green blue:blue alpha:1.0];

    //create a basic animation

    CABasicAnimation *animation = [CABasicAnimation animation];

  • animation.keyPath = @"backgroundColor";
    animation.toValue
     = (__bridge id)color.CGColor;
  • animation.delegate = self;
    //apply animation to layer

 

[self.colorLayer addAnimation:animation forKey:nil];

}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {

//set the backgroundColor property to match animation toValue

 

[CATransaction begin];

[CATransaction setDisableActions:YES];

self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;

[CATransaction commit];

}

@end

 

The problem with CAAnimation using a delegate pattern instead of a completion block is that it makes it quite awkward when you have multiple animations or animated layers to keep track of.

使用delegate pattern 而不是completion block 的问题是当你有多个动画或动画层需要追踪的时候,会显的很笨重。

When creating animations in a view controller, you would usually use the controller itself as the animation delegate (as we did in Listing 8.3), but since all the animations will be calling the same delegate method, you need some way to determine which completion call relates to which layer.

Consider the clock from Chapter 3, "Layer Geometry"; we originally implemented the clock without animation by simply updating the angle of the hands every second. It would look nicer if the hands animated to their new position realistically.

We can't animate the hands using implicit animation because the hands are represented by UIView instances, and implicit animation is disabled for their backing layers. We could animate them easily using UIView animation methods, but there is a benefit to using an explicit property animation if we want more control over the animation timing (more on this in Chapter 10). Animating those hands using CABasicAnimation is potentially quite complex because we would need to detect which hand the animation relates to in the -animationDidStop:finished: method (so we can set its finishing position).

The animation itself is passed as a parameter to the delegate method. You might be thinking that you can store the animations as properties in the controller and compare them with the parameter in the delegate method, but this won't work because the animation returned by the delegate is an immutable copy of the original, not the same object.

 

When we attached the animations to our layers using -addAnimation:forKey:, there was a key parameter that we've so far always set to nil.

当我们把动画附加到我们的layers addAnimation:forKey:有一个key参数我们总是设置为nil

The key is an NSString that is used to uniquely identify the animation if you later want to retrieve it using the layer's -animationForKey: method.

这个是一个NSString用来标识动画,如果你在稍后使用layeranimationForKey:方法。 

The keys for all animations currently attached to a layer can be retrieved using the animationKeys property. If we were to use a unique key for each animation, we could loop through the animation keys for each animated layer and compare

the result of calling -animationForKey: with the animation object passed to our delegate method. It's not exactly an elegant solution, though.

 

Fortunately, there is an easier way. Like all NSObject subclasses, CAAnimation conforms to the KVC (Key-Value Coding) ad hoc protocol, which allows you to set and get properties by name using the -setValue:forKey: and -valueForKey: methods.

幸运的是,CAAnimation顺从KVC (Key-Value Coding )协议,允许你设置和获取属性通过setValue:forKey:-valueForKey: 方法。 

But CAAnimation has an unusual feature: It acts like an NSDictionary, allowing you to set arbitrary key/value pairs even if they do not match up to any of the declared properties of the animation class that you are using.

 

This means that you can tag an animation with additional data for your own use. In this case, we will attach the clock hand UIView to the animation, so we can easily determine which view each animation relates to. We can then use this information in the delegate method to update the correct hand (see Listing 8.4).

 

 

Listing 8.4 Using KVC to Tag an Animation with Additional Data

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIImageView *hourHand;

@property (nonatomic, weak) IBOutlet UIImageView *minuteHand;

@property (nonatomic, weak) IBOutlet UIImageView *secondHand;

@property (nonatomic, weak) NSTimer *timer;

@end
@implementation
 ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//adjust anchor points

self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 

self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

 self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

//start timer

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self

selector:@selector(tick) userInfo:nil

 

repeats:YES];

//set initial hand positions

 

[self updateHandsAnimated:NO];

 

}

- (void)tick {

[self updateHandsAnimated:YES];

  • }
  • - (void)updateHandsAnimated:(BOOL)animated {

    //convert time to hours, minutes and seconds

    NSCalendar *calendar =
    [[NSCalendar
     alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit |

    NSSecondCalendarUnit;

  • NSDateComponents *components = [calendar components:units

 

fromDate:[NSDate date]];

CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;

//calculate hour hand angle //calculate minute hand angle

 

CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;

//calculate second hand angle

CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;

//rotate hands

 

[self setAngle:hourAngle forHand:self.hourHand animated:animated];

[self setAngle:minuteAngle forHand:self.minuteHand animated:animated];

[self setAngle:secondAngle forHand:self.secondHand animated:animated];

}

- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView

animated:(BOOL)animated //generate transform

{
CATransform3D
 transform = CATransform3DMakeRotation(angle, 0, 0, 1);

if (animated) {

//create transform animation

CABasicAnimation *animation = [CABasicAnimation animation];

 

animation.keyPath = @"transform";
animation.toValue
 = [NSValue valueWithCATransform3D:transform]; animation.duration = 0.5;
animation.delegate
 = self;

[animation setValue:handView forKey:@"handView"];

[handView.layer addAnimation:animation forKey:nil];

}

else

{

handView.layer.transform = transform; }

}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {

//set final position for hand view

UIView *handView = [anim valueForKey:@"handView"];

 

handView.layer.transform = [anim.toValue CATransform3DValue];

}

@end

We've successfully identified when each layer has finished animating and updated its transform to the correct value. So far, so good.

Unfortunately, even after taking those steps, we have another problem. Listing 8.4 works fine on the simulator, but if we run it on an iOS device, we can see our clock hand snap back to its original value briefly before our -animationDidStop:finished: delegate method is called. The same thing happens with the layer color in Listing 8.3.

 

The problem is that although the callback method is called after the animation has finished, there is no guarantee that it will be called before the property has been reset to its pre- animation state. This is a good example of why you should always test animation code on a device, not just on the simulator.

你需要不仅仅在模拟器上测试。

We can work around this by using a property called fillMode, which we explore in the next chapter, but the lesson here is that setting the animated property to its final value immediately before applying the animation is a much simpler approach than trying to update it after the animation has finished.

   

 

Keyframe Animations

CABasicAnimation is interesting in that it shows us the underlying mechanism behind most of the implicit animations on iOS, but adding a CABasicAnimation to a layer explicitly is a lot of work for little benefit when there are simpler ways to achieve the same effect (either using implicit animations for hosted layers, or UIView animation for views and backing layers).

 

CAKeyframeAnimation, however, is considerably more powerful and has no equivalent interface exposed in UIKit.

CAKeyframeAnimation更强大并且没有相对的UIKit

CAKeyframeAnimation is, like CABasicAnimation, a subclass of CAPropertyAnimation. It still operates on a single property, but unlike CABasicAnimation it is not limited to just a single start and end value, and instead can be given an arbitrary sequence of values to animate between.

 

 

The term keyframe originates from traditional animation, where a lead animator would draw only the frames where something significant happens (the key frames), and then the less highly skilled artists would draw the frames in between (which could be easily inferred from the keyframes). The same principle applies with CAKeyframeAnimation: You provide the significant frames, and Core Animation fills in the gaps using a process called interpolation.

同样的规则应用到CAKeyframeAnimation:,你提供重要的frameCore Animation使用一个process 称为interpolation来填充gaps

 

We can demonstrate this using our colored layer from the earlier example. We'll set up an array of colors and play them back with a single command using a keyframe animation (see Listing 8.5).

 

Listing 8.5 Applying a Sequence of Colors Using CAKeyframeAnimation

- (IBAction)changeColor {

//create a keyframe animation

 

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];

animation.keyPath = @"backgroundColor";

animation.duration = 2.0;

animation.values = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor blueColor].CGColor ];

//apply animation to layer

[self.colorLayer addAnimation:animation forKey:nil];

}

 

 

Note that we've specified blue as both the start and end color in the sequence. That is necessary because CAKeyframeAnimation does not have an option to automatically use the current value as the first frame (as we were doing with the CABasicAnimation by leaving fromValue as nil).

The animation will immediately jump to the first keyframe value when it begins, and immediately revert to the original property value once it finishes, so for a smooth animation, we need both the start and end keyframes to match the current value of the property.

 

Of course, it's possible to create animations that end on a different value than they begin. In that case, we would need to manually update the property value to match the last keyframe before we trigger the animation, just as we discussed earlier.

也可以创建一个动画与开始的值不同。在这种情况下,我们需要手动更新属性值来匹配最后的keyframe在我们触发动画之前。

 

We've increased the duration of our animation from the default of 0.25 seconds to 2 seconds using the duration property so that the animation doesn't take place too quickly to follow. If you run the animation, you'll see that the layer cycles through the colors, but the effect seems a bit... strange. The reason for this is that the animation runs at a constant pace.

原因是动画运行为一个常速。 

It does not slow down as it transitions through each color, and this results in a slightly surreal effect. To make the animation appear more natural, we will need to adjust the easing, which will be explained in Chapter 10.

 

Supplying an array of values makes sense for animating something like a color change, but it's not a very intuitive way to describe motion in general. CAKeyframeAnimation has an alternative way to specify an animation, by using a CGPath.

CAKeyframeAnimation通过使用CGPath有一个另外的方式来指明动画。

The path property allows you to define a motion sequence in an intuitive way by using Core Graphics functions to draw your animation.

path属性允许你定义一个运动序列通过使用Core Graphics 函数绘制你的动画。

 

To demonstrate this, let's animate a spaceship image moving along a simple curve. To create the path, we'll use a cubic Bézier curve, which is a special type of curve that is defined using a start and end point with two additional control points to guide the shape. It's possible to create such a path purely using C-based Core Graphics drawing commands, but it's easier to do this using the high-level UIBezierPath class provided by UIKit.

 

Although it's not actually necessary for the animation, we're going to draw the curve onscreen using a CAShapeLayer. This makes it easier to visualize what our animation is going to do. After we've drawn the CGPath, we'll use it to create a CAKeyframeAnimation, then apply it to our spaceship. Listing 8.6 shows the code, and Figure 8.1 shows the result.

 

Listing 8.6 Animating a Layer Along a Cubic Bézier Curve

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController
- (void)viewDidLoad

 

{
[super
 viewDidLoad];

//create a path

 

UIBezierPath *bezierPath = [[UIBezierPath alloc] init];

[bezierPath moveToPoint:CGPointMake(0, 150)];

[bezierPath addCurveToPoint:CGPointMake(300, 150)

controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];

//draw the path using a CAShapeLayer

 

CAShapeLayer *pathLayer = [CAShapeLayer layer];

pathLayer.path = bezierPath.CGPath;

pathLayer.fillColor = [UIColor clearColor].CGColor;

pathLayer.strokeColor = [UIColor redColor].CGColor;

pathLayer.lineWidth = 3.0f;

[self.containerView.layer addSublayer:pathLayer];

//add the ship

 

CALayer *shipLayer = [CALayer layer];

shipLayer.frame = CGRectMake(006464);

shipLayer.position = CGPointMake(0150); shipLayer.contents = (__bridge id)[UIImage imageNamed:

 

@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//create the keyframe animation

 

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];

animation.keyPath = @"position";

animation.duration = 4.0;
animation.path
 = bezierPath.CGPath;

 

[shipLayer addAnimation:animation forKey:nil];

}

@end

 

Figure 8.1 The spaceship image layer moving along a Bézier curve

If you run the example, you may notice that the ship animation looks a bit unrealistic because it's always pointing directly to the right as it moves rather than turning to match the tangent of the curve. You could animate the orientation of the ship as it moves by adjusting its affineTransform, but that would be tricky to synchronize with the other animation.

Fortunately, Apple anticipated this scenario, and added a property to CAKeyframeAnimation called rotationMode. Set rotationMode to the constant value kCAAnimationRotateAuto (see Listing 8.7), and the layer will automatically rotate to follow the tangent of the curve as it animates (see Figure 8.2).

Listing 8.7 Automatically Aligning Layer to Curve with rotationMode

- (void)viewDidLoad {

[super viewDidLoad]; //create a path

...

//create the keyframe animation

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";
animation.duration
 = 4.0;
animation.path
 = bezierPath.CGPath;

 

animation.rotationMode = kCAAnimationRotateAuto;

[shipLayer addAnimation:animation forKey:nil]; }

 

Virtual Properties

We mentioned earlier that the fact that property animations work on key paths instead of keys means that we can animate subproperties and even virtual properties. But what is a virtual property?

 

Consider a rotation animation: If we wanted to animate a rotating object, we'd have to animate the transform because there is no explicit angle/orientation property on a CALayer. We might do that as shown in Listing 8.8.

考虑到一个旋转动画:如果我们想动画一个旋转的对象,我们需要动画transform因为在CALayer没有一个明确的角度属性。

 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//add the ship

 

CALayer *shipLayer = [CALayer layer];

shipLayer.frame = CGRectMake(00128128);

shipLayer.position = CGPointMake(150150);

shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//animate the ship rotation

 

CABasicAnimation *animation = [CABasicAnimation animation];

animation.keyPath = @"transform";

animation.duration = 2.0;
animation.toValue
 = [NSValue valueWithCATransform3D:

 

CATransform3DMakeRotation(M_PI, 0, 0, 1)];

[shipLayer addAnimation:animation forKey:nil];

}

@end

This works, but it turns out that this is more by luck than design. If we were to change the rotation value from M_PI (180 degrees) to 2 * M_PI (360 degrees) and run the animation, we'd find that the ship doesn't move at all. That's because the matrix representation for a rotation of 360 degrees is the same as for 0 degrees, so as far as the animation is concerned, the value hasn't changed.

Now try using M_PI again, but set it as the byValue property instead of toValue, to indicate that the rotation should be relative to the current value. You might expect that to have the same effect as setting toValue, because 0 + 90 degrees == 90 degrees, but in fact the ship image expands instead of rotating because transform matrices cannot be added together like angular values can.

What if we want to animate the translation or scale of the ship independently of its angle? Because both of those require us to modify the transform property, we would need to recalculate the combined effect of each of those animations at each point in time and create a complex keyframe animation from the combined transform values, even though all we really want to do is animate a few conceptually discrete attributes of our layer independently.

 

Fortunately, there is a solution: To rotate the layer, we can apply our animation to the transform.rotation key path instead of animating the transform property itself (see Listing 8.9).

为了旋转layer,我们可以应用我们的animation transform.rotation key path 而不是旋转transform 属性自身。

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

 

- (void)viewDidLoad{

[super viewDidLoad];

//add the ship

 

CALayer *shipLayer = [CALayer layer];

shipLayer.frame = CGRectMake(00128128);

shipLayer.position = CGPointMake(150150);

shipLayer.contents = (__bridge id)[UIImage imageNamed:

 

@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:shipLayer];

//animate the ship rotation

 

CABasicAnimation *animation = [CABasicAnimation animation];

animation.keyPath = @"transform.rotation";

animation.duration = 2.0;

animation.byValue = @(M_PI * 2);

[shipLayer addAnimation:animation forKey:nil]; }

@end

This approach works great. The benefits of animating transform.rotation instead of the transform are as follows:

 

 It allows us to rotate more than 180 degrees in a single step, without using keyframes.

 It allows us to perform a relative rather than absolute rotation (by setting the byValue

instead of toValue).
 It allows us to specify the angle as a simple numeric value instead of constructing a

CATransform3D.

 

 It won't conflict with transform.position or transform.scale(which are also individually animatable using key paths).

The odd thing about the transform.rotation property is that it doesn't really exist. It can't exist because CATransform3D isn't an object; it's a struct and so cannot have KVC (Key-Value Coding) compliant properties. transform.rotation is actually a virtual property that CALayer provides to simplify the process of animating transforms.

You cannot set properties like transform.rotation or transform.scale directly; they can only be used for animation. When you animate these properties, Core Animation automatically updates the transform property with the actual value that your changes necessitate by using a class called CAValueFunction.

CAValueFunction is used to convert the simple floating-point value that we assign to the virtual transform.rotation property into the actual CATransform3D matrix value that is needed to position the layer. You can change the value function used by a given CAPropertyAnimation by setting its valueFunction property. The function you specify will override the default.

CAValueFunction seems like it could be a useful mechanism for animating properties that cannot naturally be summed together or interpolated (such as transform matrices), but because the implementation details of CAValueFunction are private, it's not currently possible to subclass it to create new value functions. You can only use the functions that Apple already make available as constants (which currently all relate to the transform matrix's virtual properties and are therefore somewhat redundant since the default actions for those properties already use the appropriate value functions).

Animation Groups

Although CABasicAnimation and CAKeyframeAnimation only target individual properties, multiple such animations can be gathered together using a CAAnimationGroup. CAAnimationGroup is another concrete subclass of CAAnimation that adds an animations array property, to be used for grouping other animations. Let's test this out by grouping the keyframe animation in Listing 8.6 together with another basic animation that adjusts the layer background color (see Listing 8.10). Figure 8.3 shows the result.

Adding an animation group to a layer is not fundamentally different from adding the animations individually, so it's not immediately clear when or why you would use this class. It provides some convenience in terms of being able to collectively set animation durations, or add and remove multiple animations from a layer with a single command, but it's usefulness only really becomes apparent when it comes to hierarchical timing, which is explained in Chapter 9.

 

@interface ViewController ()
@property(strong,nonatomic)UIView
 *layerView;


@end

@implementation
 ViewController

- (void)viewDidLoad {
    [super
 viewDidLoad];
   
    [self.view
 addSubview:self.layerView];
   
 //create a BeizerPath
   
 UIBezierPath *bezierPath=[[UIBezierPath alloc ]init];
    [bezierPath
 moveToPoint:CGPointMake(0, 150)];
    [bezierPath
 addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
   
 //drawing the path using a CAShaperLayer
   
 CAShapeLayer *pathLayer=[CAShapeLayer layer];
    pathLayer.path=bezierPath.CGPath;
    pathLayer.fillColor=[UIColor
 clearColor].CGColor;
    pathLayer.strokeColor=[UIColor
 redColor].CGColor;
    pathLayer.lineWidth=3.0f;
    [self.layerView.layer
 addSublayer :pathLayer];
   
   
   
 CALayer *colorLayer = [CALayer layer];
    colorLayer.frame
 = CGRectMake(0, 0, 64, 64);
    colorLayer.position
 = CGPointMake(0, 150);
    colorLayer.backgroundColor
 = [UIColor greenColor].CGColor;
    [self.layerView.layer
 addSublayer:colorLayer];
   
   
   
 CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animation];
    keyframeAnimation.keyPath=@"position";
   
    keyframeAnimation.path=bezierPath.CGPath;
    keyframeAnimation.rotationMode=kCAAnimationRotateAuto;
   
   
 CABasicAnimation *basicAnimation=[CABasicAnimation animation];
    basicAnimation.keyPath=@"backgroundColor";
    basicAnimation.toValue=(__bridge
 id)[UIColor redColor].CGColor;
   
   
   
 //create group animation
   
 CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
    animationGroup.animations=@[keyframeAnimation,basicAnimation];
    animationGroup.duration=4.0f;
    [colorLayer
 addAnimation:animationGroup forKey:nil];
   
   

}


-(UIView*)layerView{
   
 if (!_layerView) {
       
 _layerView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
       
    }
   
 return _layerView;

}

Figure 8.3 A grouped keyframe path and basic color property animation

Transitions

 

Sometimes with iOS applications it is necessary to make layout changes that are very difficult to animate using property animations. You might need to swap some text or an image, for example, or replace a whole grid or table of views at once. Property animations only work on animatable properties of a layer, so if you need to change a nonanimatable property (such as an image) or actually add and remove layers from the hierarchy, property animations won't work.

当需要改变不是属性动画的值的时候,就要用到transitions.

 

This is where transitions come in. A transition animation does not try to smoothly interpolate between two values like a property animation; instead it is designed as a sort of distraction tactic—to cover up content changes with an animation. Transitions affect an entire layer instead of just a specific property. The transition takes a snapshot of the old layer appearance and then animates in the new appearance in a single sweep.

transition 对久的CALayer有一个快照,然后就动画到新的appearance

 

To create a transition, we use CATransition, another subclass of CAAnimation. In addition to all the timing functions and so on that it inherits from CAAnimation, CATransition has a type and a subtype that are used to specify the transition effect.

CATransition 有一个type和一个subtype 影响transition的效果 

The type property is an NSString and can be set to one of the following constant values:

kCATransitionFade kCATransitionMoveIn kCATransitionPush kCATransitionReveal

You are currently limited to these four basic CATransition types, but there are some ways that you can achieve additional transition effects, as covered later in the chapter.

The default transition type is kCATransitionFade, which creates a smooth crossfade between the previous layer appearance and the new appearance after you have modified its properties or contents.

We made use of the kCATransitionPush type in the custom action example in Chapter 7; this slides the new layer appearance in from the side, pushing the old one out of the opposite side.

kCATransitionMoveIn and kCATransitionReveal are similar to kCATransitionPush; they both implement a directional swipe animation, but with subtle differences; kCATransitionMoveIn moves the new layer appearance in over the top of the previous appearance, but doesn't push it out to the side like the push transition, and kCATransitionReveal slides the old appearance out to reveal the new one instead of sliding the new one in.

 

The latter three standard transition types are inherently directional in nature. By default, they slide from the left, but you can control their direction using the subtype property, which accepts one of the following constants:

默认情况下,他们从左侧划入,但是你可以用subtype 属性进行控制。

kCATransitionFromRight kCATransitionFromLeft kCATransitionFromTop kCATransitionFromBottom

A simple example of using CATransition to animate a nonanimatable property is shown in Listing 8.11. Here we are changing the image property of a UIImage, which cannot normally be animated using either implicit animation or a CAPropertyAnimation because Core Animation doesn't know how to interpolate between images. By using a crossfade transition applied to the layer, however, we make it possible to smoothly animate the change regardless of the content type (see Figure 8.4). Try changing the transition type constant to see the other possible effects.

 

Listing8.11 Animating a UIImageView Using CATransition 

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIImageView *imageView;

@property (nonatomic, copy) NSArray *images;

@end
@implementation
 ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//set up images

self.images = @[[UIImage imageNamed:@"Anchor.png"], [UIImage imageNamed:@"Cone.png"],

 

[UIImage imageNamed:@"Igloo.png"], [UIImage imageNamed:@"Spaceship.png"]];

}

 

 

- (IBAction)switchImage {

//set up crossfade transition

 

CATransition *transition = [CATransition animation];

transition.type = kCATransitionFade;

//apply transition to imageview backing layer

[self.imageView.layer addAnimation:transition forKey:nil];

//cycle to next image

 

UIImage *currentImage = self.imageView.image;

NSUInteger index = [self.images indexOfObject:currentImage];

index = (index + 1) % [self.images count];

self.imageView.image = self.images[index];

}

@end

 

As you can see from the code, transitions are added to a layer in the same way as property or group animations, using the -addAnimation:forKey: method. Unlike property animations, though, only one CATransition can operate on a given layer at a time. For this reason, regardless of what value you specify for the key, the transition will actually be attached with a key of "transition", which is represented by the constant kCATransition.

 

 

Figure 8.4 Smoothly crossfading between images using CATransition

 

Implicit Transitions

The fact that CATransition can smoothly cover any changes made to a layer makes it an ideal candidate for use as a layer action for properties that are otherwise hard to animate. Apple realizes this, of course, and CATransition is used as the default action when setting the CALayer contents property. This is disabled for view backing layers along with all other implicit animation actions, but for layers that you create yourself, this means that changes to the layer contents image are automatically animated with a crossfade.

We used a CATransition as a layer action in Chapter 7 to animate changes to our layer's background color. The backgroundColor property can be animated using a normal CAPropertyAnimation but that doesn't mean you can't use a CATransition instead.

Animating Layer Tree Changes

The fact that CATransition does not operate on specific layer properties means that you can use it to animate layer changes even when you do not know exactly what has changed. You can, for example, smoothly cover the reloading of a complex UITableView with a crossfade without needing to know which rows have been added and removed, or transition between two different UIViewController instances without needing to know anything about their internal view hierarchies.

Both of these cases are different from anything we've tried so far because they involve animating not only changes to a layer's properties but actual layer tree changes—we need to physically add and remove layers from the hierarchy during the course of the animation.

The trick in this case is to ensure that the layer the CATransition is attached to will not itself be removed from the tree during the transition, because then the CATransition will be removed along with it. Generally, you just need to attach the transition to the superlayer of the layers that are affected.

In Listing 8.12, we show how you can implement a crossfade transition between tabs in a UITabBarController. Here we have simply taken the default Tabbed Application project template and used the -tabBarController:didSelectViewController: method of the UITabBarControllerDelegate to apply the transition animation. We've attached the transition to the UITabBarController view's layer because that doesn't get replaced when the tabs themselves are swapped.

Listing 8.12 Animating a UITabBarController

 

#import "AppDelegate.h"

#import "FirstViewController.h"

#import "SecondViewController.h"

#import <QuartzCore/QuartzCore.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];

UIViewController *viewController1 = [[FirstViewController alloc] init]; UIViewController *viewController2 = [[SecondViewController alloc] init]; self.tabBarController = [[UITabBarController alloc] init]; self.tabBarController.viewControllers = @[viewController1,

 

viewController2];

 

 

self.tabBarController.delegate = self;

self.window.rootViewController = self.tabBarController; [self.window makeKeyAndVisible];

return YES;

}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController

{

 

//set up crossfade transition

 

CATransition *transition = [CATransition animation];

transition.type = kCATransitionFade;

//apply transition to tab bar controller's view

 

[self.tabBarController.view.layer addAnimation:transition forKey:nil];

}

@end

Custom Transitions 通用transitions 

We've established that transitions are a powerful way to animate properties that would otherwise be difficult to change smoothly. But the list of animation types for CATransition seems a bit limited.

What's even stranger is that Apple exposes Core Animation's transition feature via the UIView +transitionFromView:toView:duration:options:completion: and +transitionWithView:duration:options:animations: methods, but the transition options available are completely different to the constants made available via the CATransition type property. The constants that can be specified for the UIView transition method options parameter are as follows:

 

UIViewAnimationOptionTransitionFlipFromLeft UIViewAnimationOptionTransitionFlipFromRight

UIViewAnimationOptionTransitionCurlUp

UIViewAnimationOptionTransitionCurlDown

UIViewAnimationOptionTransitionCrossDissolve

UIViewAnimationOptionTransitionFlipFromTop UIViewAnimationOptionTransitionFlipFromBottom

With the exception of UIViewAnimationOptionTransitionCrossDissolve, none of these transitions correspond to the CATransition types. You can test these alternative transitions by using a modified version of our earlier transition example (see Listing 8.13).

 

Listing 8.13 Alternative Transition Implementation Using UIKit Methods

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIImageView *imageView;

@property (nonatomic, copy) NSArray *images;

@end
@implementation
 ViewController

- (void)viewDidLoad {

[super viewDidLoad]; //set up images

self.images = @[[UIImage imageNamed:@"Anchor.png"], [UIImage imageNamed:@"Cone.png"],

 

[UIImage imageNamed:@"Igloo.png"], [UIImage imageNamed:@"Spaceship.png"]];

}

 

 

- (IBAction)switchImage {

[UIView transitionWithView:self.imageView duration:1.0

options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{

//cycle to next image

 

UIImage *currentImage = self.imageView.image;

NSUInteger index = [self.images indexOfObject:currentImage];

index = (index + 1) % [self.images count]; self.imageView.image = self.images[index];

 

}

completion:NULL]; }

@end

The documentation in some places seems to imply that since iOS 5 (which introduced the Core Image framework), it might be possible to use CIFilter in conjunction with the filter property of CATransition to create additional transition types. As of iOS 6, however, this still does not work. Attempting to use Core Image filters with CATransition has no effect. (This is supported on Mac OS, which probably accounts for the documentation discrepancies.)

For this reason, you are forced to choose between using a CATransition or the UIView transition method, depending on the effect you want. Hopefully, a future version of iOS will add support for Core Image transition filters and so make the full range of Core Image transition animations available via CATransition (and maybe even add the ability to create new ones).

 

That doesn't mean that it's impossible to achieve custom transition effects on iOS, though. It just means that you have to do a bit of extra work. As mentioned earlier, the basic principle of a transition animation is that you take a snapshot of the current state of the layer and then apply an animation to that snapshot while you change the layer behind the scenes. If we can figure out how to take a snapshot of our layer, we can perform the animation ourselves using ordinary property animations without using CATransition or UIKit's transition methods at all.

如果我们知道怎样获得当前的快照,也就知道如何添加动画了。

As it turns out, taking a snapshot of a layer is relatively easy. CALayer has a -renderInContext: method that can be used to capture an image of its current contents by drawing it into a Core Graphics context, which can then be displayed in another view. If we place this snapshot view in front of the original, it will mask any changes we make to the real view's contents, allowing us to re-create the effect of a simple transition.

Listing 8.14 demonstrates a basic implementation of this idea: We take a snapshot of the current view state and then spin and fade out the snapshot while we change the background color of the original view. Figure 8.5 shows our custom transition in progress.

To keep things simple, we've performed the animation using the UIView -animateWithDuration:completion: method. Although it would be possible to perform the exact same effect using CABasicAnimation, we would have to set up separate animations for the layer transform and opacity properties and implement the CAAnimationDelegate to remove coverView from the screen once the animation has completed.

 

Listing 8.14 Creating a Custom Transition Using renderInContext:

@implementation ViewController

- (IBAction)performTransition {

//preserve the current view snapshot

 

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0); [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();

//insert snapshot view in front of this one

 

UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];

coverView.frame = self.view.bounds;

[self.view addSubview:coverView];

//update the view (we'll simply randomize the layer background color)

 

CGFloat red = arc4random() / (CGFloat)INT_MAX;

CGFloat green = arc4random() / (CGFloat)INT_MAX;

CGFloat blue = arc4random() / (CGFloat)INT_MAX;

self.view.backgroundColor = [UIColor colorWithRed:red

green:green blue:blue alpha:1.0];

//perform animation (anything you like)

[UIView animateWithDuration:1.0 animations:^{

//scale, rotate and fade the view

 

CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);

 

transform = CGAffineTransformRotate(transform, M_PI_2);

coverView.transform = transform;

coverView.alpha = 0.0;

} completion:^(BOOL finished){

//remove the cover view now we're finished with it

[coverView removeFromSuperview]; }];

}

@end

Figure 8.5 A custom transition implemented using renderInContext:

 

There is a caveat to this approach: The -renderInContext: method captures the layer's backing image and sublayers, but does not correctly handle transforms applied to those sublayers, and doesn't work with video or OpenGL content. CATransition doesn't seem to be affected by this limitation, so is presumably using a private method to capture the snapshot.

 

Canceling an Animation in Progress

As mentioned earlier in this chapter, you can use the key parameter of the -addAnimation:forKey: method to retrieve an animation after it has been added to a layer by using the following method:

- (CAAnimation *)animationForKey:(NSString *)key;

Modifying animations once they are in progress is not supported, so the primary uses of this are to check what the animation properties are or to detect whether a particular animation is attached to the layer.

To terminate a specific animation, you can remove it from the layer using the following method:

 

- (void)removeAnimationForKey:(NSString *)key; Or you can just remove all animations using this method: - (void)removeAllAnimations;

为了终结一个指定的动画,你可以通过- (void)removeAnimationForKey:(NSString *)key;来移除。

As soon as an animation is removed, the layer appearance updates to match its current model value. Animations are removed automatically when they finish unless you specify that they shouldn't by setting the removedOnCompletion property of the animation to NO. If you set the animation to not be removed automatically, it is important to remove it yourself when it is no longer needed; otherwise, it will stay in memory until the layer itself is eventually destroyed.

Let's extend our rotating ship example once again with buttons to stop and start the animation. This time, we provide a non-nil value for our animation key so that we can remove it later. The flag argument in the -animationDidStop:finished: method indicates whether the animation finished naturally or was interrupted, which we log in the console. If you terminate the animation using the stop button, it will log NO, but if you allow it to complete, it will log YES.

See Listing 8.15 for the updated example code. Figure 8.6 shows it in action.

 

Listing 8.15 Starting and Stopping an Animation

@interface ViewController ()

 

@property (nonatomic, weak) IBOutlet UIView *containerView;

@property (nonatomic, strong) CALayer *shipLayer;

 

@end

@implementation ViewController

-void)viewDidLoad {

[super viewDidLoad];

//add the ship

 

self.shipLayer = [CALayer layer];

self.shipLayer.frame = CGRectMake(0, 0, 128, 128);

self.shipLayer.position = CGPointMake(150, 150);

self.shipLayer.contents = (__bridge id)[UIImage imageNamed:

 

@"Ship.png"].CGImage;

[self.containerView.layer addSublayer:self.shipLayer];

 

}

 

- (IBAction)start {

//animate the ship rotation

 

CABasicAnimation *animation = [CABasicAnimation animation];

animation.keyPath = @"transform.rotation"; animation.duration = 2.0;

animation.byValue = @(M_PI * 2);

animation.delegate = self;

 

[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];

}

-(IBAction)stop {

 

[self.shipLayer removeAnimationForKey:@"rotateAnimation"];

}

 

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {

//log that the animation stopped

NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO"); }

 

@end

 

Figure 8.6 A rotation animation controlled by start and stop buttons

 

Summary

In this chapter, we covered property animations (which allow you to exert very specific control over the animation of individual layer properties), animation groups (which allow you to combine multiple property animations into a single unit), and transitions (which affect an entire layer and can be used to animate any sort of change to the layer's contents, including the addition and removal of sublayers).

 

In Chapter 9, we study the CAMediaTiming protocol and discover how Core Animation deals with the passage of time.

posted @ 2015-09-14 15:40  巴山夜雨1989  阅读(410)  评论(0编辑  收藏  举报