[关闭]
@Xiaojun-Jin 2015-03-31 08:58 字数 11412 阅读 1792

Animation Programming with Core Animaiton

iOS Animation


In most cases, animations can be accomplished using higher-level UIKit methods, however, Core Animation will give you a better understanding of what is going on. It also allows for a more explicit way of describing animations.

Additive property in CABasicAnimation

Enable continuously & smoothly animation using additive property in CABasicAnimation. additive When true the value specified by the animation will be "added" to the current presentation value of the property to produce the new presentation value.

Example:

Approach 1: (more realistic)

If there is an animation moving an object from (20, 100) to (100, 100) with 2s duration, then we need to move back the object animately while the previous animation is executing. In this case, we can see the object rush to (100, 100) and then back to (20, 100) animately.

  1. CABasicAnimation *animation = [CABasicAnimation animation];
  2. animation.keyPath = @"position.y";
  3. animation.fromValue = @(88-330);
  4. animation.toValue = @0;
  5. animation.duration = 2;
  6. animation.additive = YES;
  7. // better easing function
  8. animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:.5 :0 :.5 :1];
  9. self.object.layer.position = CGPointMake(self.object.center.x, 330);
  10. [self.object.layer addAnimation:animation forKey:@"Ani-1"];
  11. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  12. (int64_t)(0.8 * NSEC_PER_SEC)),
  13. dispatch_get_main_queue(), ^
  14. {
  15. CABasicAnimation *animation = [CABasicAnimation animation];
  16. animation.keyPath = @"position.y";
  17. animation.fromValue = @(330-88);
  18. animation.toValue = @0;
  19. animation.duration = 2;
  20. animation.additive = YES;
  21. animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:.5 :0
  22. :.5 :1];
  23. self.object.layer.position = CGPointMake(self.object.center.x, 88);
  24. [self.object.layer addAnimation:animation forKey:@"Ani-2"];
  25. });

With the above code, we can create an smoothly animation easily, note that the key for animation should be different, and without timingFunction the animation may stay still about 0.5s while two animation is transitioning.

http://netcetera.org/camtf-playground.html (CAMediaTimingFunction playground)

Approach 2:

  1. CABasicAnimation *animation = [CABasicAnimation animation];
  2. animation.keyPath = @"position.y";
  3. animation.fromValue = @(88);
  4. animation.toValue = @330;
  5. animation.duration = 2;
  6. self.object.layer.position = CGPointMake(self.object.center.x, 330);
  7. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  8. [self.object.layer addAnimation:animation forKey:@"basic"];
  9. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  10. (int64_t)(0.8 * NSEC_PER_SEC)),
  11. dispatch_get_main_queue(), ^
  12. {
  13. CABasicAnimation *animation = [CABasicAnimation animation];
  14. animation.keyPath = @"position.y";
  15. animation.fromValue = @([self.object.layer.presentationLayer position].y);
  16. animation.toValue = @88;
  17. animation.duration = 2;
  18. animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:.5 :0 :.5 :1];
  19. self.object.layer.position = CGPointMake(self.object.center.x, 88);
  20. [self.object.layer addAnimation:animation forKey:@"basic1"];
  21. });
  • Model layer: If we modify property of an layer, the model layer will be modified immediately.
  • Presentation layer: The rending layer in the screen.

e.g. set an layer's position from (0, 100) to (100, 100) animately, model layer->(100, 100). presentation layer->(0~100, 100).

Make sure to check the complete list of supported key paths

When we run this code, we realise that our rocket jumps back to its initial position as soon as the animation is complete. To deal with this issue, wen can update the property directly on the model layer. This is the recommended approach, since it makes the animation completely optional.

Once the animation completes and is removed from the layer, the presentation layer will fall through to the value that is set on the model, which matches the last step of the animation:

  1. CABasicAnimation *animation = [CABasicAnimation animation];
  2. animation.keyPath = @"position.x"; animation.fromValue = @77;
  3. animation.toValue = @455; animation.duration = 1;
  4. [rocket.layer addAnimation:animation forKey:@"basic"];
  5. rocket.layer.position = CGPointMake(455, 61);

A Multi-Stage Animation

Keyframes allow us to define an arbitrary number of points during the animation, and then let Core Animation fill in the so-called in-betweens.

  1. CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
  2. animation.keyPath = @"position.x";
  3. animation.values = @[ @0, @10, @-10, @10, @0 ];
  4. animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ];
  5. animation.duration = 0.4;
  6. animation.additive = YES;
  7. [form.layer addAnimation:animation forKey:@"shake"];

Animation Along a Path

Thankfully, CAKeyframeAnimation offers the more convenient path property as an alternative. For instance, this is how we would animate a view in a circle:

  1. CGRect boundingRect = CGRectMake(-150, -150, 300, 300);
  2. CAKeyframeAnimation *orbit = [CAKeyframeAnimation animation];
  3. orbit.keyPath = @"position";
  4. orbit.path = CFAutorelease(CGPathCreateWithEllipseInRect(boundingRect, NULL));
  5. orbit.duration = 4;
  6. orbit.additive = YES;
  7. orbit.repeatCount = HUGE_VALF;
  8. orbit.calculationMode = kCAAnimationPaced;
  9. orbit.rotationMode = kCAAnimationRotateAuto;
  10. [satellite.layer addAnimation:orbit forKey:@"orbit"];

Animating the Drawings

One of the nice little additions in iOS SDK 4.2 are two new properties for CAShapeLayer: strokeStart and strokeEnd. Both are floats that hold a value between 0.0 and 1.0, indicating the relative location along the shape layer's path at which to start and stop stroking the path.

  1. CGRect pathRect = CGRectInset(self.animationLayer.bounds, 100.0f, 100.0f);
  2. CGPoint roofTip = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMaxY(pathRect));
  3. CGPoint bottomLeft = CGPointMake(CGRectGetMinX(pathRect), CGRectGetMinY(pathRect));
  4. CGPoint bottomRight = CGPointMake(CGRectGetMaxX(pathRect), CGRectGetMinY(pathRect));
  5. CGPoint topLeft = CGPointMake(CGRectGetMinX(pathRect),
  6. CGRectGetMinY(pathRect) + CGRectGetHeight(pathRect) * 2.0f/3.0f);
  7. CGPoint topRight = CGPointMake(CGRectGetMaxX(pathRect),
  8. CGRectGetMinY(pathRect) + CGRectGetHeight(pathRect) * 2.0f/3.0f);
  9. UIBezierPath *path = [UIBezierPath bezierPath];
  10. [path moveToPoint:bottomLeft];
  11. [path addLineToPoint:topLeft];
  12. [path addLineToPoint:roofTip];
  13. [path addLineToPoint:topRight];
  14. [path addLineToPoint:topLeft];
  15. [path addLineToPoint:bottomRight];
  16. [path addLineToPoint:topRight];
  17. [path addLineToPoint:bottomLeft];
  18. [path addLineToPoint:bottomRight];
  19. CAShapeLayer *pathLayer = [CAShapeLayer layer];
  20. pathLayer.frame = self.animationLayer.bounds;
  21. pathLayer.bounds = pathRect;
  22. pathLayer.geometryFlipped = YES;
  23. pathLayer.path = path.CGPath;
  24. pathLayer.strokeColor = [[UIColor blackColor] CGColor];
  25. pathLayer.fillColor = nil;
  26. pathLayer.lineWidth = 10.0f;
  27. pathLayer.lineJoin = kCALineJoinBevel;
  28. [self.animationLayer addSublayer:pathLayer];
  29. self.pathLayer = pathLayer;
  30. UIImage *penImage = [UIImage imageNamed:@"pen.png"];
  31. CALayer *penLayer = [CALayer layer];
  32. penLayer.contents = (id)penImage.CGImage;
  33. penLayer.anchorPoint = CGPointZero;
  34. penLayer.frame = CGRectMake(0.0f, 0.0f, penImage.size.width, penImage.size.height);
  35. [pathLayer addSublayer:penLayer];
  36. self.penLayer = penLayer;

The really nice thing about these properties is that they are animatable. By animating strokeEnd from 0.0 to 1.0 over a duration of a few seconds, we can easily display the path as it is being drawn:

  1. CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  2. pathAnimation.duration = 10.0;
  3. pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
  4. pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
  5. [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];

Finally, add a second layer containing the image of a pen and use a CAKeyframeAnimation to animate it along the path with the same speed to make the illusion perfect:

  1. CAKeyframeAnimation *penAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  2. penAnimation.duration = 10.0;
  3. penAnimation.path = self.pathLayer.path;
  4. penAnimation.calculationMode = kCAAnimationPaced;
  5. [self.penLayer addAnimation:penAnimation forKey:@"penAnimation"];

Timing Functions

You'll notice that there is something very artificial about the animation of our rocket. That is because most movements we see in the real world take time to accelerate or decelerate. Objects that instantly reach their top speed and then stop immediately tend to look very unnatural. Unless you're dancing the robot, that’s rarely a desired effect.

  1. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

Animation Groups

For certain complex effects, it may be necessary to animate multiple properties at once. You can see that we have to animate the position, rotation and z-position of the artworks at once. Using CAAnimationGroup, the code to animate one of the covers could look a little something like this:

  1. CABasicAnimation *zPosition = [CABasicAnimation animation];
  2. zPosition.keyPath = @"zPosition";
  3. zPosition.fromValue = @-1;
  4. zPosition.toValue = @1;
  5. zPosition.duration = 1.2;
  6. CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
  7. rotation.keyPath = @"transform.rotation";
  8. rotation.values = @[ @0, @0.14, @0 ];
  9. rotation.duration = 1.2;
  10. rotation.timingFunctions = @[
  11. [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
  12. [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
  13. ];
  14. CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
  15. position.keyPath = @"position";
  16. position.values = @[
  17. [NSValue valueWithCGPoint:CGPointZero],
  18. [NSValue valueWithCGPoint:CGPointMake(110, -20)],
  19. [NSValue valueWithCGPoint:CGPointZero]
  20. ];
  21. position.timingFunctions = @[
  22. [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
  23. [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
  24. ];
  25. position.additive = YES;
  26. position.duration = 1.2;
  27. CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
  28. group.animations = @[ zPosition, rotation, position ];
  29. group.duration = 1.2;
  30. group.beginTime = 0.5;
  31. [card.layer addAnimation:group forKey:@"shuffle"];
  32. card.layer.zPosition = 1;

Animating Custom Layer Properties

By default, almost every standard property of CALayer and its subclasses can be animated, either by adding a CAAnimation to the layer (explicit animation), or by specifying an action for the property and then modifying it (implicit animation).

But sometimes we may wish to animate several properties in concert as if they were a single animation, or we may need to perform an animation that cannot be implemented by applying animations to standard layer properties.

We don't actually need to write any animation code at all if the properties we are setting already have standard animation actions set up, because if we modify those properties, they will inherit whatever animation settings are configured in the current CATransaction, and will animate automatically.

  1. // oval
  2. self.bounds = CGRectMake(0, 0, 200, 200);
  3. self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
  4. self.fillColor = [UIColor whiteColor].CGColor;
  5. self.strokeColor = [UIColor blackColor].CGColor;
  6. self.lineWidth = 4;
  7. // rectangle or line
  8. self.hourHand = [CAShapeLayer layer];
  9. self.hourHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-2, -70, 4, 70)].CGPath;
  10. self.hourHand.fillColor = [UIColor blackColor].CGColor;
  11. self.hourHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
  12. [self addSublayer:self.hourHand];
  13. // we uses NSCalendar to break the time down into hours and minutes,
  14. // which we then convert into angular coordinates.
  15. NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
  16. NSDateComponents *components = [calendar components:NSHourCalendarUnit | NSMinuteCalendarUnit
  17. fromDate:time];
  18. self.hourHand.affineTransform = CGAffineTransformMakeRotation(components.hour/12.0*2.0*M_PI);
  19. self.minuteHand.affineTransform = CGAffineTransformMakeRotation(components.minute/60.0*2.0*M_PI);

Custom Container View Controller Transitions

If you need to brush up on view controller containment, introduced in iOS 5, make sure to read Ricky Gregersen's "View Controller Containment" in the very first issue.

The components of the iOS 7 custom view controller transition API are mostly protocols which make them extremely flexible to work with, because you can very easily plug them into your existing class hierarchy.

Transition Coordinators providing methods to run other animations in parallel with the transition animations. In this article, we will concentrate on non-interactive transitions. Enough talk, let's get our hands dirty…

You can download the sample code associated with this post from my GitHub page linked here.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注