How to create custom easing function with Core Animation?

拜拜、爱过 提交于 2019-11-26 13:56:25

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];

}

Not sure if you're still looking, but PRTween looks fairly impressive in terms of its ability to go beyond what Core Animation gives you out of the box, most notably, custom timing functions. It also comes packaged with many—if not all—of the popular easing curves that various web frameworks provide.

I took Jesse Crossen's response, and expanded on it a bit. You can use it to animate CGPoints, and CGSize for example. As of iOS 7, you can also use arbitrary time functions with UIView animations.

You can check out the results at https://github.com/jjackson26/JMJParametricAnimation

A swift version implementation is TFAnimation.The demo is a sin curve animation.Use TFBasicAnimation just like CABasicAnimation except assign timeFunction with a block other than timingFunction.

The key point is subclass CAKeyframeAnimation and calculate frames position by timeFunction in 1 / 60fps s interval .After all add all the calculated value to values of CAKeyframeAnimation and the times by interval to keyTimes too.

I created a blocks based approach, that generates an animation group, with multiple animations.

Each animation, per property, can use 1 of 33 different parametric curves, a Decay timing function with initial velocity, or a custom spring configured to your needs.

Once the group is generated, it's cached on the View, and can be triggered using an AnimationKey, with or without the animation. Once triggered the animation is synchronized accordingly the presentation layer's values, and applied accordingly.

The framework can be found here FlightAnimator

Here is an example below:

struct AnimationKeys {
    static let StageOneAnimationKey  = "StageOneAnimationKey"
    static let StageTwoAnimationKey  = "StageTwoAnimationKey"
}

...

view.registerAnimation(forKey: AnimationKeys.StageOneAnimationKey, maker:  { (maker) in

    maker.animateBounds(toValue: newBounds,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.animatePosition(toValue: newPosition,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.triggerTimedAnimation(forKey: AnimationKeys.StageTwoAnimationKey,
                           onView: self.secondaryView,
                           atProgress: 0.5, 
                           maker: { (makerStageTwo) in

        makerStageTwo.animateBounds(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryBounds)

        makerStageTwo.animatePosition(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryCenter)
     })                    
})

To trigger the animation

view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!