Custom buffer function (buffer 10.2) and custom buffer function 10.2
Custom buffer function
In chapter 8, we added an animation to the Clock Project. It looks good, but it would be better if there is a suitable buffer function. In the world of display, when the clock pointer turns, it usually starts very slowly, and then it immediately pops up, finally buffering to the end. But how can we create a new one for every standard buffer function?
Besides+functionWithName:
Besides,CAMediaTimingFunction
There is also another constructor, one with four floating point parameters+functionWithControlPoints::::
(Note that the strange syntax here does not contain the name of each specific parameter. This is legal in objective-C, but violates Apple's Guidelines for method naming, and it looks like a strange design ).
Using this method, we can create a custom buffer function to match our clock animation. To understand how to use this method, we need to know someCAMediaTimingFunction
How it works.
Cubic besell Curve
CAMediaTimingFunction
The main principle of a function is that it converts the input time into a proportional change between the start point and the end point. We can use a simple icon to explain that the horizontal axis represents time, and the vertical axis represents the amount of change, so the linear buffer is a simple diagonal line starting from the starting point (Figure 10.1 ).
Figure 10.1 linear buffer function Image
The slope of this curve represents the speed, and the change in the slope represents the acceleration. In principle, any accelerated curve can be expressed using this image,CAMediaTimingFunction
ACubic besell CurveFunction, which can only generate a subset of the specified buffer function.CAKeyframeAnimation
Path ).
You may recall that a cubic Bessert curve is defined by four vertices. The first and last vertices represent the starting and ending points of the curve, and the remaining two vertices are calledControl PointBecause they control the shape of the curve, the control points of the beiser curve are actually points outside the curve, that is, the curves do not have to pass through them. You can think of them as magnets that draw through their curves.
Figure 10.2 shows an example of a three-time besell buffer function
Figure 10.2 three-way besell buffer function
In fact, it is a very strange function. It accelerates first, then slows down, and finally accelerates again when it reaches the end. How can we use the standard buffer function to represent it with images?
CAMediaTimingFunction
There is-getControlPointAtIndex:values:
The method can be used to retrieve curve points. The design of this method is indeed a bit strange (maybe only Apple can answer why it does not simply returnCGPoint
), But we can use it to find the point of the standard buffer function, and then useUIBezierPath
AndCAShapeLayer
To draw it out.
The starting and ending points of the curve are always {0, 0} and {1, 1}. Therefore, we only need to retrieve the second and third points (Control Points) of the curve ). For specific code, see list 10.4. The image of all the standard buffer functions is shown in Figure 10.3.
Usage in listing 10.4UIBezierPath
DrawCAMediaTimingFunction
1 @interface ViewController () 2 3 @property (nonatomic, weak) IBOutlet UIView *layerView; 4 5 @end 6 7 @implementation ViewController 8 9 - (void)viewDidLoad10 {11 [super viewDidLoad];12 //create timing function13 CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];14 //get control points15 CGPoint controlPoint1, controlPoint2;16 [function getControlPointAtIndex:1 values:(float *)&controlPoint1];17 [function getControlPointAtIndex:2 values:(float *)&controlPoint2];18 //create curve19 UIBezierPath *path = [[UIBezierPath alloc] init];20 [path moveToPoint:CGPointZero];21 [path addCurveToPoint:CGPointMake(1, 1)22 controlPoint1:controlPoint1 controlPoint2:controlPoint2];23 //scale the path up to a reasonable size for display24 [path applyTransform:CGAffineTransformMakeScale(200, 200)];25 //create shape layer26 CAShapeLayer *shapeLayer = [CAShapeLayer layer];27 shapeLayer.strokeColor = [UIColor redColor].CGColor;28 shapeLayer.fillColor = [UIColor clearColor].CGColor;29 shapeLayer.lineWidth = 4.0f;30 shapeLayer.path = path.CGPath;31 [self.layerView.layer addSublayer:shapeLayer];32 //flip geometry so that 0,0 is in the bottom-left33 self.layerView.layer.geometryFlipped = YES;34 }35 36 @end
View Code
Figure 10.3 StandardCAMediaTimingFunction
Buffer Curve
Then, for our buffer function with a custom clock pointer, we need to first faint, then quickly rise, and finally buffer the curve to the end. After some experiments, the final result is as follows:
[CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];
If you convert it to the buffer function image, as shown in the last 10.4, if you add it to the clock program, as a result (see generation list 10.5 ).
Figure 10.4 custom buffer functions suitable for clock
In listing 10.5, a clock program with a custom buffer function is added.
1 - (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated 2 { 3 //generate transform 4 CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1); 5 if (animated) { 6 //create transform animation 7 CABasicAnimation *animation = [CABasicAnimation animation]; 8 animation.keyPath = @"transform"; 9 animation.fromValue = [handView.layer.presentationLayer valueForKey:@"transform"];10 animation.toValue = [NSValue valueWithCATransform3D:transform];11 animation.duration = 0.5;12 animation.delegate = self;13 animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];14 //apply animation15 handView.layer.transform = transform;16 [handView.layer addAnimation:animation forKey:nil];17 } else {18 //set transform directly19 handView.layer.transform = transform;20 }21 }
Figure 10.5 an animation of a bounce that cannot be described using a three-time besell Curve
This effect cannot be expressed by a simple three-bysel curve, so it cannot be used.CAMediaTimingFunction
. However, to achieve this effect, you can use the following methods:
- Use
CAKeyframeAnimation
Create an animation and divide it into several steps. each small step uses its own timing function (For details, refer to the following section ).
- Use the timer to update the animation frame by frame (see Chapter 11th, "timer-based animation ").
Key Frame-based buffer
To use a key frame for bounce animation, we need to create a key frame for each significant point in the buffer curve (in this case, the key point is the peak value of each bounce ), then, the buffer function is used to connect each curve. At the same time, we also need to passkeyTimes
Specify the Time Offset of each key frame. Because the time of each bounce is reduced, the key frames are not evenly distributed.
Listing 10.6 shows the code for implementing the bullet ball animation (see Figure 10.6)
Listing 10.6 using a key frame for bullet ball Animation
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nonatomic, strong) UIImageView *ballView;@end@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; //add ball image view UIImage *ballImage = [UIImage imageNamed:@"Ball.png"]; self.ballView = [[UIImageView alloc] initWithImage:ballImage]; [self.containerView addSubview:self.ballView]; //animate [self animate];}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ //replay animation on tap [self animate];}- (void)animate{ //reset ball to top of screen self.ballView.center = CGPointMake(150, 32); //create keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 1.0; animation.delegate = self; animation.values = @[ [NSValue valueWithCGPoint:CGPointMake(150, 32)], [NSValue valueWithCGPoint:CGPointMake(150, 268)], [NSValue valueWithCGPoint:CGPointMake(150, 140)], [NSValue valueWithCGPoint:CGPointMake(150, 268)], [NSValue valueWithCGPoint:CGPointMake(150, 220)], [NSValue valueWithCGPoint:CGPointMake(150, 268)], [NSValue valueWithCGPoint:CGPointMake(150, 250)], [NSValue valueWithCGPoint:CGPointMake(150, 268)] ]; animation.timingFunctions = @[ [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut], [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn] ]; animation.keyTimes = @[@0.0, @0.3, @0.5, @0.7, @0.8, @0.9, @0.95, @1.0]; //apply animation self.ballView.layer.position = CGPointMake(150, 268); [self.ballView.layer addAnimation:animation forKey:nil];}@end
View Code
Figure 10.6 bullet ball animation using key frames
This method is not bad, but it is relatively cumbersome to implement (because it is necessary to constantly calculate various key frames and time offsets) and it is strongly bound to the animation (because if you want to change an animation attribute, it means to recalculate all the key frames ). So how can we write a method and use a buffer function to convert any simple attribute animation into a Key Frame Animation? Let's implement it.
Process Automation
In listing 10.6, we split the Animation into several relatively large parts, and then use the buffer of Core Animation to enter and buffer the exit function to form the desired curve. But if we divide an animation into smaller parts, we can splice these curves (linear buffering) with straight lines ). To achieve automation, we need to know how to do the following two things:
- Auto splits any property animation into multiple key frames
- Use a mathematical function to represent elastic animation, making frames cheaper.
To solve the first problem, we need to copy the interpolation mechanism of Core Animation. This is a mechanism for passing in the start and end points, and then generating a new point between the two points at a specified time point. For a simple floating point start value, the formula is as follows (assuming that the time ranges from 0 to 1 ):
1 value = (endValue – startValue) × time + startValue;
If you want to insertCGPoint
,CGColorRef
OrCATransform3D
For this more complex type of value, we can simply apply this method to each independent element (that isCGPoint
The x and y values in,CGColorRef
In red, blue, green, transparent value, orCATransform3D
Coordinates of the independent matrix ). We also need some logic to split the value of the object before interpolation, and then re-encapsulate it into an object after interpolation, that is, we need to check the type in real time.
Once we can use code to obtain arbitrary interpolation between the starting values of an attribute animation, we can divide the animation into many independent key frames and then generate a linear key frame animation. Listing 10.7 shows the relevant code.
Note that we use 60 x Animation time (in seconds) as the number of key frames. At this time, because Core Animation renders the screen update based on 60 frames per second, so if we generate 60 key frames per second, we can ensure that the animation is smooth enough (although it is very likely that we can achieve good results with a lower frame rate ).
In this example, we only introduceCGPoint
Type Interpolation code. However, the Code clearly shows how to scale to support other types. As an alternative to unrecognized types, we only return the first halffromValue
In the last halftoValue
.
Listing 10.7 create a Key Frame Animation with the inserted Value
1 float interpolate(float from, float to, float time) 2 { 3 return (to - from) * time + from; 4 } 5 6 - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time 7 { 8 if ([fromValue isKindOfClass:[NSValue class]]) { 9 //get type10 const char *type = [fromValue objCType];11 if (strcmp(type, @encode(CGPoint)) == 0) {12 CGPoint from = [fromValue CGPointValue];13 CGPoint to = [toValue CGPointValue];14 CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));15 return [NSValue valueWithCGPoint:result];16 }17 }18 //provide safe default implementation19 return (time < 0.5)? fromValue: toValue;20 }21 22 - (void)animate23 {24 //reset ball to top of screen25 self.ballView.center = CGPointMake(150, 32);26 //set up animation parameters27 NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];28 NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];29 CFTimeInterval duration = 1.0;30 //generate keyframes31 NSInteger numFrames = duration * 60;32 NSMutableArray *frames = [NSMutableArray array];33 for (int i = 0; i < numFrames; i++) {34 float time = 1 / (float)numFrames * i;35 [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];36 }37 //create keyframe animation38 CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];39 animation.keyPath = @"position";40 animation.duration = 1.0;41 animation.delegate = self;42 animation.values = frames;43 //apply animation44 [self.ballView.layer addAnimation:animation forKey:nil];45 }
View Code
This can play a role, but the effect is not very good. So far, all we have done is a very complicated method to use linear buffer replication.CABasicAnimation
. The advantage of this method is that we can control the buffer more accurately, which also means we can apply a fully customized buffer function. So what should we do?
The mathematics behind the buffer is not very simple, but fortunately we don't need to implement it one by one. Robert Penna has a Web page about the buffer function (http://www.robertpenner.com/easing), which contains links to the implementation of most universal buffer functions in multiple programming languages, including C. Here is an example of buffering into the buffer to exit the function (in fact there are many different ways to implement it ).
1 float quadraticEaseInOut(float t) 2 {3 return (t < 0.5)? (2 * t * t): (-2 * t * t) + (4 * t) - 1; 4 }
View Code
For our elastic ball, we can usebounceEaseOut
Function:
1 float bounceEaseOut(float t) 2 { 3 if (t < 4/11.0) { 4 return (121 * t * t)/16.0; 5 } else if (t < 8/11.0) { 6 return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0; 7 } else if (t < 9/10.0) { 8 return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0; 9 }10 return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;11 }
If you modify the code in listing 10.7 To introducebounceEaseOut
Method. Our task is to only swap the buffer function. Now we can select any buffer type to create an animation (see Figure 10.8 ).
Listing 10.8 using key frames to implement custom buffering Functions
1 - (void)animate 2 { 3 //reset ball to top of screen 4 self.ballView.center = CGPointMake(150, 32); 5 //set up animation parameters 6 NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)]; 7 NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)]; 8 CFTimeInterval duration = 1.0; 9 //generate keyframes10 NSInteger numFrames = duration * 60;11 NSMutableArray *frames = [NSMutableArray array];12 for (int i = 0; i < numFrames; i++) {13 float time = 1/(float)numFrames * i;14 //apply easing15 time = bounceEaseOut(time);16 //add keyframe17 [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];18 }19 //create keyframe animation20 CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];21 animation.keyPath = @"position";22 animation.duration = 1.0;23 animation.delegate = self;24 animation.values = frames;25 //apply animation26 [self.ballView.layer addAnimation:animation forKey:nil];27 }