如何使用Core Animation创build自定义缓动function?
我在iOS中很好地沿着CGPath
(QuadCurve)制作CALayer
。 但是我想使用一个比苹果公司提供的less数有趣的缓动function(EaseIn / EaseOut等)。 例如,弹跳或弹性function。
MediaTimingFunction(bezier)可以做这些事情:
但是我想创build更复杂的定时function 。 问题是媒体时机似乎需要一个立方贝塞尔,这是不够强大的创造这些效果:
_media/manual/transitions.png
在其他框架中创build上述代码很简单,这使得这非常令人沮丧。 请注意,曲线将input时间映射到输出时间(Tt曲线),而不是时间 – 位置曲线。 例如, easeOutBounce(T)= t返回一个新的t 。 那么这个t被用来绘制运动(或者我们应该animation的任何属性)。
所以,我想创build一个复杂的自定义CAMediaTimingFunction
但我不知道该怎么做,或者甚至有可能? 有没有其他的select?
编辑:
以下是步骤的具体示例。 非常教育:)
-
我想要从a点到b点的一条线上创build一个对象的animation,但是我希望它使用上面的easeOutBounce曲线沿着线“反弹”它的运动。 这意味着它将遵循从a到b的确切的线,但是将以比当前基于bezier的CAMediaTimingFunction更加复杂的方式进行加速和减速。
-
让我们用CGPath指定任意曲线运动。 它仍然应该沿着这条曲线移动,但是它应该像在示例中那样加速和减速。
理论上我认为它应该像这样工作:
让我们将运动曲线描述为关键帧animation移动(t)= p ,其中t是时间[0..1], p是在时间t计算的位置。 所以移动(0)返回曲线起点处的位置, 移动(0.5)正确的中间点,结束移动(1) 。 使用定时函数time(T)= t来提供移动的t值应该给我我想要的。 对于弹跳效果,定时函数应该返回时间(0.8)和时间(0.8)相同的t值(只是一个例子)。 只需更换计时function即可获得不同的效果。
(是的,可以通过创build和连接四个线段来进行线反弹,但这不是必须的,毕竟这只是一个简单的线性函数,它将时间值映射到位置)。
我希望我在这里有道理。
我find了这个:
可爱的爱 – 核心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]; }
我知道这可能不像你想要的那么简单,但这是一个开始。
从iOS 10开始,使用两个新的时序对象可以更容易地创build定制的定时function。
1) UICubicTimingParameters允许定义三次Bézier曲线作为一个缓动函数。
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)
或者简单地使用animation初始化上的控制点
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)
这个令人敬畏的服务将帮助您为曲线select控制点。
2) UISpringTimingParameters使开发人员可以操纵阻尼比 , 质量 , 刚度和初始速度来创build所需的弹簧行为。
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)
持续时间参数仍在animation制作工具中呈现,但在弹簧时机上将被忽略。
如果这两个选项不够,你也可以通过确认UITimingCurveProvider协议来实现你自己的时间曲线。
更多细节,如何创build具有不同时间参数的animation,可以在文档中find。
另请参阅WWDC 2016的UIKitanimation和转换演示 。
创build自定义定时函数的一种方法是使用CAMediaTimingFunction中的functionWithControlPoints ::::工厂方法(还有一个相应的initWithControlPoints ::: init方法)。 这是为您的计时function创build一个贝塞尔曲线。 这不是一个任意的曲线,但贝塞尔曲线非常强大和灵活。 需要一点练习才能掌握控制点。 提示:大多数绘图程序可以创build贝塞尔曲线。 玩这些将会给你一个关于你用控制点表示的曲线的视觉反馈。
这个链接指向苹果的文档。 关于如何从曲线构build预构build函数,有一个简短而有用的部分。
编辑:下面的代码显示了一个简单的反弹animation。 为此,我创build了一个组合的计时函数( 值和时间 NSArray属性),并给animation的每个段不同的时间长度( keytimes属性)。 通过这种方式,您可以构buildBézier曲线,为animation创作更复杂的时序。 这是一个很好的文章,在这种types的animation与一个很好的示例代码。
- (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]; }
不知道你是否还在寻找,但是PRTween看起来相当不错,它超越了Core Animation给你提供的开箱即用function,最值得注意的是定制的定时function。 它还包含许多(如果不是全部的话)各种Web框架提供的stream行的缓动曲线。
我接受了杰西·克罗森的回应,并对其进行了扩展。 您可以使用它来animationCGPoints和CGSize例如。 从iOS 7开始,您还可以使用UIViewanimation的任意时间函数。
你可以在https://github.com/jjackson26/JMJParametricAnimation查看结果;
一个快速的版本实现是TFAnimation 。演示是一个正弦曲线animation。使用TFBasicAnimation
就像CABasicAnimation
一样,除了赋值timeFunction
而不是timingFunction
。
关键点是CAKeyframeAnimation
子类,并以1 / 60fps
的时间间隔通过timeFunction
计算帧的位置。所有的计算值都加上了CAKeyframeAnimation
values
,间隔的时间也加到了keyTimes
。
我创build了一个基于块的方法,用多个animation生成一个animation组。
每个animation,每个属性,可以使用33种不同的参数曲线中的一种,具有初始速度的衰减定时function,或根据需要configuration自定义弹簧。
一旦这个组被生成,它被caching在视图上,并且可以使用一个AnimationKey来触发,有或没有animation。 一旦触发animation就相应地同步表示层的值,并相应地应用。
这个框架可以在这里findFlightAnimator
下面是一个例子:
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) }) })
触发animation
view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)