如何使用 Core Animation 创建自定义缓动功能?
Posted
技术标签:
【中文标题】如何使用 Core Animation 创建自定义缓动功能?【英文标题】:How to create custom easing function with Core Animation? 【发布时间】:2011-03-01 23:01:05 【问题描述】:我在 ios 中很好地沿 CGPath
(QuadCurve) 为 CALayer
制作动画。但我想使用比 Apple 的少数 provided 更有趣的缓动功能(EaseIn/EaseOut 等)。例如,反弹或弹性功能。
这些事情可以用 MediaTimingFunction (bezier) 来做:
但我想创建更复杂的计时函数。问题是媒体时间似乎需要三次贝塞尔曲线,它不足以产生这些效果:
(来源:sparrow-framework.org)
创建上面的code 在其他框架中很简单,这使得这很令人沮丧。请注意,这些曲线将输入时间映射到输出时间(T-t 曲线),而不是时间-位置曲线。例如,easeOutBounce(T) = t 返回一个新的t。然后那个 t 用于绘制运动(或我们应该动画的任何属性)。
所以,我想创建一个复杂的自定义 CAMediaTimingFunction
,但我不知道该怎么做,或者是否有可能?有没有其他选择?
编辑:
以下是步骤中的具体示例。很有教育意义:)
我想沿从点 a 到 b 的直线为对象设置动画,但我希望它使用上面的easeOutBounce 曲线。这意味着它将遵循从 a 到 b 的精确线,但会以比使用当前基于贝塞尔曲线的 CAMediaTimingFunction 更复杂的方式加速和减速。
让这条线可以任意曲线移动,由 CGPath 指定。它仍应沿该曲线移动,但应以与直线示例中相同的方式加速和减速。
理论上我认为它应该这样工作:
让我们将运动曲线描述为关键帧动画move(t) = p,其中t 是时间[0..1],p em> 是在时间 t 计算的位置。所以 move(0) 返回曲线开始处的位置,move(0.5) 返回精确的中间位置,move(1) 返回曲线结束处。使用计时函数 time(T) = t 为 move 提供 t 值应该可以满足我的需求。对于弹跳效果,计时函数应该为 time(0.8) 和 time(0.8) 返回相同的 t 值(仅作为示例) .只需更换定时功能即可获得不一样的效果。
(是的,可以通过创建和连接四个来回的线段来进行线反弹,但这不是必需的。毕竟,它只是一个映射时间的简单线性函数 值到位置。)
我希望我在这里说得通。
【问题讨论】:
请注意(在 SO 上经常出现这种情况),这个非常老的问题现在有非常过时的答案 .. 一定要向下滚动到最新的答案。 (非常值得注意:cubic-bezier.com!) 【参考方案1】:我发现了这个:
Cocoa with Love - Parametric acceleration curves in Core Animation
但我认为使用块可以让它变得更简单和更具可读性。所以我们可以在 CAKeyframeAnimation 上定义一个看起来像这样的类别:
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
现在的用法如下所示:
// 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];
我知道这可能不像你想要的那么简单,但这是一个开始。
【讨论】:
我接受了 Jesse Crossen 的回复,并对其进行了一些扩展。您可以使用它来为 CGPoints 和 CGSize 设置动画。从 iOS 7 开始,您还可以对 UIView 动画使用任意时间函数。您可以在github.com/jjackson26/JMJParametricAnimation 查看结果 @J.J.Jackson 很棒的动画收藏!感谢您收集它们【参考方案2】:从 iOS 10 开始,可以使用两个新的计时对象更轻松地创建自定义计时功能。
1) UICubicTimingParameters 允许将三次 Bézier curve 定义为缓动函数。
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)
This awesome service 将帮助您选择曲线的控制点。
2) UISpringTimingParameters 让开发人员可以操纵阻尼比、质量、刚度和初速度来创建所需的弹簧行为。
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 参数仍然存在于 Animator 中,但会在 spring 计时中被忽略。
如果这两个选项还不够,您还可以通过确认 UITimingCurveProvider 协议来实现自己的时序曲线。
更多细节,如何创建不同时序参数的动画,你可以在the documentation找到。
另外,请参阅来自 WWDC 2016 的 Advances in UIKit Animations and Transitions presentation。
【讨论】:
你可以在iOS10-animations-demo repository找到使用Timing函数的例子。 您能否详细说明“如果这两个选项还不够,您还可以通过确认 UITimingCurveProvider 协议来实现自己的时序曲线。” ? 太棒了!并且CoreAnimation
版本的UISpringTimingParameters
(CASpringAnimation
) 支持回iOS 9!
@OlgaKonoreva ,cubic-bezier.com 服务是绝对无价和美妙。希望你仍然是一个 SO 用户 - 给你发一个胖子赏金来表示感谢:)
@ChristianSchnorr 我认为答案是UITimingCurveProvider
不仅可以表示立方或弹簧动画,还可以表示动画通过UITimingCurveType.composed
结合了立方和弹簧。【参考方案3】:
创建自定义计时函数的一种方法是使用 CAMediaTimingFunction 中的 functionWithControlPoints:::: 工厂方法(也有相应的 initWithControlPoints:::: init 方法)。这样做是为您的计时功能创建一条贝塞尔曲线。它不是任意曲线,但贝塞尔曲线非常强大和灵活。掌握控制点需要一点练习。提示:大多数绘图程序都可以创建贝塞尔曲线。玩这些会给你一个关于你用控制点表示的曲线的视觉反馈。
this link 指向苹果的文档。关于如何从曲线构造预构建函数,有一个简短但有用的部分。
编辑: 以下代码显示了一个简单的弹跳动画。为此,我创建了一个组合计时函数(values 和 timing NSArray 属性)并为动画的每个片段赋予不同的时间长度(keytimes 财产)。通过这种方式,您可以组合 Bézier 曲线来组合更复杂的动画时序。 This 是一篇关于此类动画的好文章,并附有很好的示例代码。
- (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 的 values 和 timingFunctions 属性组合多个计时函数来实现您想要的。我会做一个例子,把代码放一点。 感谢您的努力,并为尝试加分 :) 该方法可能在理论上可行,但需要针对每次使用进行定制。我正在寻找一种适用于所有动画作为计时功能的通用解决方案。老实说,将线条和曲线拼接在一起看起来并不那么好。 “盒子里的杰克”的例子也受此影响。 您可以为关键帧动画指定 CGPathRef 而不是值数组,但最终 Felz 是正确的——CAKeyframeAnimation 和 timingFunctions 将为您提供所需的一切。我不确定为什么您认为这不是必须“针对每种用途进行定制”的“通用解决方案”。您可以在工厂方法或其他方法中构建一次关键帧动画,然后可以根据需要多次将该动画添加到任何层。为什么需要针对每次使用进行定制?在我看来,这与您关于计时功能应该如何工作的概念没有什么不同。 哦,伙计们,现在已经晚了 4 年,但如果 functionWithControlPoints:::: 完全可以制作有弹性/有弹性的动画。与cubic-bezier.com/#.6,1.87,.63,.74 结合使用【参考方案4】:不确定您是否还在寻找,但PRTween 看起来相当令人印象深刻,因为它超越了 Core Animation 为您提供的开箱即用功能,尤其是自定义计时功能。它还包含许多(如果不是全部)各种 Web 框架提供的流行缓动曲线。
【讨论】:
【参考方案5】:一个swift版本的实现是TFAnimation。演示是一个sin曲线动画。使用TFBasicAnimation
就像CABasicAnimation
一样,除了将timeFunction
分配给timingFunction
以外的块。
关键点是CAKeyframeAnimation
的子类,并在1 / 60fps
s区间内通过timeFunction
计算帧位置。将所有计算值添加到CAKeyframeAnimation
的values
,并将时间间隔添加到keyTimes
也是。
【讨论】:
【参考方案6】:我创建了一个基于块的方法,它生成一个包含多个动画的动画组。
每个动画,每个属性,可以使用 33 条不同的参数曲线中的 1 条、具有初始速度的 Decay 计时函数或根据您的需要配置的自定义弹簧。
一旦组生成,它就会被缓存在 View 上,并且可以使用 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)
【讨论】:
以上是关于如何使用 Core Animation 创建自定义缓动功能?的主要内容,如果未能解决你的问题,请参考以下文章
iOS Core Animation Advanced Techniques-显式动画