How to create custom easing function with Core Animation?
I am animating a CALayer
along a CGPath
(QuadCurve) quite nicely in iOS. But I'd like to use a more interesting easing function than the few provided by Apple (EaseIn/EaseOut etc). For instance, a bounce or elastic function.
These things are possible to do with MediaTimingFunction (bezier):
But I'd like to create timing functions that are more complex. Problem is that media timing seems to require a cubic bezier which is not powerful enough to create these effects:
http://wiki.sparrow-framework.org/_media/manual/transitions.png
The code to create the above is simple enough in other frameworks, which makes this very frustrating. Note that the curves are mapping input time to output time (Tt curve) and not time-position curves. For instance, easeOutBounce(T) = t returns a new t. Then that t is used to plot the movement (or whatever property we should animate).
So, I'd like to create a complex custom CAMediaTimingFunction
but I have no clue how to do that, or if it's even possible? Are there any alternatives?
EDIT:
Here is a concrete example in to steps. Very educational :)
I want to animate an object along a line from point a to b, but I want it to "bounce" its movement along the line using the easeOutBounce curve above. This means it will follow the exact line from a to b, but will accelerate and decelerate in a more complex way than what is possible using the current bezier-based CAMediaTimingFunction.
Lets make that line any arbitrary curve movement specified with CGPath. It should still move along that curve, but it should accelerate and decelerate the same way as in the line example.
In theory I think it should work like this:
Lets describe the movement curve as a keyframe animation move(t) = p, where t is time [0..1], p is position calculated at time t. So move(0) returns the position at the start of curve, move(0.5) the exact middle and move(1) at end. Using a an timing function time(T) = t to provide the t values for move should give me what I want. For a bouncing effect, the timing function should return the same t values for time(0.8) and time(0.8) (just an example). Just replace the timing function to get a different effect.
(Yes, it's possible to do line-bouncing by creating and joining four line segments which goes back and forth, but that shouldn't be necessary. After all, it's just a simple linear function which maps time values to positions.)
I hope I'm making sense here.
I found this:
Cocoa with Love - Parametric acceleration curves in Core Animation
But I think it can be made a little simpler and more readable by using blocks. So we can define a category on CAKeyframeAnimation that looks something like this:
CAKeyframeAnimation+Parametric.h:
// this should be a function that takes a time value between
// 0.0 and 1.0 (where 0.0 is the beginning of the animation
// and 1.0 is the end) and returns a scale factor where 0.0
// would produce the starting value and 1.0 would produce the
// ending value
typedef double (^KeyframeParametricBlock)(double);
@interface CAKeyframeAnimation (Parametric)
+ (id)animationWithKeyPath:(NSString *)path
function:(KeyframeParametricBlock)block
fromValue:(double)fromValue
toValue:(double)toValue;
CAKeyframeAnimation+Parametric.m:
@implementation CAKeyframeAnimation (Parametric)
+ (id)animationWithKeyPath:(NSString *)path
function:(KeyframeParametricBlock)block
fromValue:(double)fromValue
toValue:(double)toValue {
// get a keyframe animation to set up
CAKeyframeAnimation *animation =
[CAKeyframeAnimation animationWithKeyPath:path];
// break the time into steps
// (the more steps, the smoother the animation)
NSUInteger steps = 100;
NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps];
double time = 0.0;
double timeStep = 1.0 / (double)(steps - 1);
for(NSUInteger i = 0; i < steps; i++) {
double value = fromValue + (block(time) * (toValue - fromValue));
[values addObject:[NSNumber numberWithDouble:value]];
time += timeStep;
}
// we want linear animation between keyframes, with equal time steps
animation.calculationMode = kCAAnimationLinear;
// set keyframes and we're done
[animation setValues:values];
return(animation);
}
@end
Now usage will look something like this:
// define a parametric function
KeyframeParametricBlock function = ^double(double time) {
return(1.0 - pow((1.0 - time), 2.0));
};
if (layer) {
[CATransaction begin];
[CATransaction
setValue:[NSNumber numberWithFloat:2.5]
forKey:kCATransactionAnimationDuration];
// make an animation
CAAnimation *drop = [CAKeyframeAnimation
animationWithKeyPath:@"position.y"
function:function fromValue:30.0 toValue:450.0];
// use it
[layer addAnimation:drop forKey:@"position"];
[CATransaction commit];
}
I know it might not be quite as simple as what you wanted, but it's a start.
From iOS 10 it became possible to create custom timing function easier using two new timing objects.
1) UICubicTimingParameters allows to define cubic Bézier curve as an easing function.
let cubicTimingParameters = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1))
let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTimingParameters)
or simply using control points on animator initialization
let controlPoint1 = CGPoint(x: 0.25, y: 0.1)
let controlPoint2 = CGPoint(x: 0.25, y: 1)
let animator = UIViewPropertyAnimator(duration: 0.3, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
This awesome service is going to help to choose control points for your curves.
2) UISpringTimingParameters lets developers manipulate damping ratio, mass, stiffness, and initial velocity to create desired spring behavior.
let velocity = CGVector(dx: 1, dy: 0)
let springParameters = UISpringTimingParameters(mass: 1.8, stiffness: 330, damping: 33, initialVelocity: velocity)
let springAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)
Duration parameter is still presented in Animator, but will be ignored for spring timing.
If these two options are not enough you also can implement your own timing curve by confirming to the UITimingCurveProvider protocol.
More details, how to create animations with different timing parameters, you can find in the documentation.
Also, please, see Advances in UIKit Animations and Transitions presentation from WWDC 2016.
A way to create a custom timing function is by using the functionWithControlPoints:::: factory method in CAMediaTimingFunction (there is a corresponding initWithControlPoints:::: init method as well). What this does is create a Bézier curve for your timing function. It is not an arbitrary curve, but Bézier curves are very powerful and flexible. It takes a little practice to get the hang of the control points. A tip: most drawing programs can create Bézier curves. Playing with those will give you a visual feedback on the curve you are representing with the control points.
The this link points to apple's documentation. There is a short but useful section on how the pre-build functions are constructed from curves.
Edit: The following code shows a simple bounce animation. For doing so, I created a composed timing function ( values and timing NSArray properties) and gave each segment of the animation a different time length ( keytimes property). In this way you can compose Bézier curves to compose more sophisticated timing for animations. This is a good article on this type of animations with a nice sample code.
- (void)viewDidLoad {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)];
v.backgroundColor = [UIColor redColor];
CGFloat y = self.view.bounds.size.height;
v.center = CGPointMake(self.view.bounds.size.width/2.0, 50.0/2.0);
[self.view addSubview:v];
//[CATransaction begin];
CAKeyframeAnimation * animation;
animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
animation.duration = 3.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
NSMutableArray *values = [NSMutableArray array];
NSMutableArray *timings = [NSMutableArray array];
NSMutableArray *keytimes = [NSMutableArray array];
//Start
[values addObject:[NSNumber numberWithFloat:25.0]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
[keytimes addObject:[NSNumber numberWithFloat:0.0]];
//Drop down
[values addObject:[NSNumber numberWithFloat:y]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseOut)];
[keytimes addObject:[NSNumber numberWithFloat:0.6]];
// bounce up
[values addObject:[NSNumber numberWithFloat:0.7 * y]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
[keytimes addObject:[NSNumber numberWithFloat:0.8]];
// fihish down
[values addObject:[NSNumber numberWithFloat:y]];
[keytimes addObject:[NSNumber numberWithFloat:1.0]];
//[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
animation.values = values;
animation.timingFunctions = timings;
animation.keyTimes = keytimes;
[v.layer addAnimation:animation forKey:nil];
//[CATransaction commit];
}
链接地址: http://www.djcxy.com/p/39488.html
上一篇: 改变正在进行的动画