How to create custom easing function with Core Animation?

我在 iOS 中沿着 CGPath(QuadCurve)很好地制作了一个 CALayer动画。但我想使用一个比苹果公司的少数 提供(EaseIn/EaseOut 等)更有趣的宽松功能。例如,弹跳或弹性功能。

These things are possible to do with MediaTimingFunction (bezier):

enter image description here

但我想创建更复杂的 计时功能。问题是,媒体的时机选择似乎需要一个立方贝塞尔,而这个立方贝塞尔的力量不足以产生这些效果:

enter image description here
(source: sparrow-framework.org)

在其他框架中,创建以上代码的 密码非常简单,这使得它非常令人沮丧。请注意,曲线是映射输入时间到输出时间(T-t 曲线) ,而不是时间位置曲线。例如,EaseOutBounce (T) = t返回一个新的 T。然后,使用 T绘制运动(或者我们应该动画的任何属性)。

因此,我想创建一个复杂的自定义 CAMediaTimingFunction,但我不知道如何做到这一点,或者它是否可能?还有别的选择吗?

编辑:

这里有一个具体的例子,非常有教育意义:)

  1. 我想要动画一个物体沿着一条线从点 B,但我希望它沿着线“反弹”它的运动使用上面的 easeOutBounce 曲线。这意味着它将遵循从 B的精确路线,但是将以比使用当前基于 bezier 的 CAMediaTimingfunction 更复杂的方式加速和减速。

  2. 让我们用 CGPath 指定任意曲线移动该直线。它仍然应该沿着这条曲线移动,但是它应该以与线性示例相同的方式加速和减速。

理论上,我认为应该是这样的:

让我们将运动曲线描述为一个关键帧动画 move(t) = p,其中 T是时间[0。。1] ,P是在时间 T计算的位置。因此,move(0)返回曲线开始的位置,移动(0.5)返回曲线中间的位置,而 移动(1)返回曲线结束的位置。使用一个计时函数 时间(T) = t提供 让开T值应该可以给我我想要的。对于弹跳效果,计时函数应该返回相同的 T1和 T1的 T值(只是一个例子)。只要替换定时函数就可以得到不同的效果。

(是的,可以通过创建和连接来回移动的四个线段来进行线弹跳,但这应该没有必要。毕竟,它只是一个将 时间值映射到位置的简单线性函数。)

我希望我说的有道理。

53531 次浏览

创建定制计时函数的一种方法是使用 CAMediaTimingfunction 中的 FunctionWithControlPoints: : : :工厂方法(还有一个相应的 initWithControlPoint: : : init 方法)。这样做是为你的计时函数创建一个贝塞尔曲线。它不是任意的曲线,但贝塞尔曲线是非常强大和灵活的。掌握控制点的窍门需要一点练习。提示: 大多数绘图程序可以创建 Bézier 曲线。玩这些游戏会给你一个关于你用控制点表示的曲线的视觉反馈。

这个链接指向苹果的文档。有一个简短但有用的部分,关于如何从曲线构造预构建函数。

编辑: 下面的代码显示了一个简单的弹跳动画。为此,我创建了一个组合计时函数(价值观时机 NSArray 属性) ,并为动画的每个片段赋予了不同的时间长度(按键时间属性)。通过这种方式,您可以构成 Bézier 曲线,为动画构成更复杂的计时。这个是一篇关于这类动画的优秀文章,提供了很好的示例代码。

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


}

我发现了这个:

可可与爱-核心动画中的参数化加速度曲线

但是我认为通过使用块可以使它变得更简单和更易读。所以我们可以在 CAKeyframeAnimation 上定义一个类别,它看起来像这样:

CAKeyframeAnimation + Parameter tric.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 + Parameter tric.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

现在的用法是这样的:

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

我知道这可能不像你想要的那么简单,但这是个开始。

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.

一个快速的版本实现是 TFAnimation。演示是一个 罪恶曲线动画。像使用 CABasicAnimation一样使用 TFBasicAnimation,只是将 timeFunction分配给 timingFunction以外的块。

关键点是 CAKeyframeAnimation子类,利用 timeFunction计算帧在 1 / 60fps间隔内的位置。毕竟把所有的计算值加到 CAKeyframeAnimationvalues上,把每个时间间隔的次数也加到 keyTimes上。

我创建了一个基于块的方法,生成一个动画组,包含多个动画。

每个动画,每个属性,可以使用1的33个不同的参数曲线,衰减时间函数与初始速度,或自定义弹簧配置到您的需要。

一旦组生成,它就被缓存在视图中,并且可以使用 AnimationKey 触发,不管有没有动画。一旦触发动画,相应地同步表示层的值,并相应地应用。

框架可以在这里找到 FlightAnimator

下面是一个例子:

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)
})
})

来触发动画

view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)

从 iOS10开始,使用两个新的计时对象可以更容易地创建自定义计时函数。

1) < strong > UICubicTimingParameter 允许定义立方 贝塞尔曲线作为缓和函数。

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)

或者仅仅使用动画初始化的控制点

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)

这个令人敬畏的服务 将帮助你选择曲线的控制点。

2) < strong > UISpringTimingParameter 允许开发人员操纵 阻尼比质量僵硬initial velocity来创建期望的弹簧行为。

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)

持续时间参数仍然在 Animator 中显示,但是对于弹簧计时将被忽略。

如果这两个选项还不够,你也可以通过确认 曲线提供程序协议来实现你自己的计时曲线。

更多细节,如何创建具有不同时间参数的动画,您可以在 文件中找到。

另外,请参阅2016年全球开发者大会的 Advances in UIKit Animations and Transitions presentation