我在iOS中很好地在CALayer
(QuadCurve)上设置CGPath
动画。但我想使用一个比Apple(EaseIn / EaseOut等)少数provided更有趣的缓动函数。例如,弹跳或弹性功能。
这些可能与MediaTimingFunction(bezier)有关:
但我想创建更复杂的计时功能。问题是媒体时间似乎需要一个立方贝塞尔,它不足以产生这些效果:
http://wiki.sparrow-framework.org/_media/manual/transitions.png
创建上述内容的code在其他框架中非常简单,这使得这非常令人沮丧。请注意,曲线是将输入时间映射到输出时间(T-t曲线)而不是时间 - 位置曲线。例如, easeOutBounce(T)= t 会返回一个新的 t 。然后 t 用于绘制运动(或任何我们应该制作动画的属性)。
所以,我想创建一个复杂的自定义CAMediaTimingFunction
,但我不知道如何做到这一点,或者它是否可能?还有其他选择吗?
修改
以下是步骤的具体示例。很有教育意义:))
我想沿着从 a 到 b 的一条线来设置一个对象的动画,但是我想让它沿着这条线“弹回”它的运动。上面的easeOutBounce曲线。这意味着它将遵循从 a 到 b 的确切行,但会以比使用当前基于bezier的CAMediaTimingFunction更复杂的方式加速和减速。
让我们使用CGPath指定任意曲线移动。它应该仍沿着该曲线移动,但它应该以与行示例相同的方式加速和减速。
从理论上讲,我认为它应该是这样的:
让我们将运动曲线描述为关键帧动画 move(t)= p ,其中 t 是时间[0..1], p 是在 t 时计算的位置。所以 move(0)返回曲线开头的位置, move(0.5)确切的中间, move(1)结束。使用定时函数 time(T)= t 为 move 提供 t 值应该给我想要的东西。对于弹跳效果,计时功能应为时间(0.8)和时间(0.8)返回相同的 t 值(仅举例) 。只需更换计时功能即可获得不同的效果。
(是的,可以通过创建和连接来回的四个线段进行线条弹跳,但这不是必需的。毕竟,它只是一个简单的线性函数,它映射时间< / em>值到位置。)
我希望我在这里有意义。
答案 0 :(得分:47)
我发现了这个:
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];
}
我知道它可能不像你想要的那么简单,但它是一个开始。
答案 1 :(得分:27)
从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)
持续时间参数仍在Animator中显示,但弹出时间将被忽略。
如果这两个选项不够,您还可以通过确认 UITimingCurveProvider 协议来实现自己的时序曲线。
更多详细信息,如何创建具有不同时序参数的动画,您可以在the documentation中找到。
另外,请参阅WWDC 2016的Advances in UIKit Animations and Transitions presentation。
答案 2 :(得分:13)
创建自定义计时功能的一种方法是在CAMediaTimingFunction中使用 functionWithControlPoints :::: 工厂方法(还有一个相应的initWithControlPoints :::: init方法)。这样做是为您的计时功能创建Bézier曲线。它不是任意曲线,但Bézier曲线非常强大且灵活。获取控制点的挂起需要一些练习。小贴士:大多数绘图程序都可以创建Bézier曲线。使用这些将为您提供关于您使用控制点表示的曲线的视觉反馈。
this link指向苹果的文档。关于如何根据曲线构建预构建函数,有一个简短但有用的部分。
编辑: 以下代码显示了一个简单的反弹动画。为此,我创建了一个组合计时功能(值和计时 NSArray属性),并为动画的每个片段赋予不同的时间长度(关键时间财产)。通过这种方式,您可以组合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];
}
答案 3 :(得分:9)
不确定你是否还在寻找,但是PRTween在能够超越Core Animation开箱即用的能力方面看起来相当令人印象深刻,最明显的是自定义计时功能。它还包含许多(如果不是全部)各种Web框架提供的流行缓动曲线。
答案 4 :(得分:4)
我接受了杰西·克罗斯森的回应,并对此进行了扩展。您可以使用它来为CGPoints和CGSize设置动画。从iOS 7开始,您还可以在UIView动画中使用任意时间函数。
查看结果答案 5 :(得分:1)
快速版本的实现是TFAnimation。演示是一个 sin 曲线动画。使用TFBasicAnimation
就像CABasicAnimation
一样,除了分配timeFunction
除timingFunction
以外的其他块。
关键点是子类CAKeyframeAnimation
,并在timeFunction
s区间内按1 / 60fps
计算帧位置。然后将所有计算值添加到values
CAKeyframeAnimation
和间隔的时间也是keyTimes
。
答案 6 :(得分:1)
我创建了一个基于块的方法,用于生成具有多个动画的动画组。
每个属性的每个动画可以使用33种不同参数曲线中的1种,具有初始速度的衰减计时功能,或根据您的需要配置的自定义弹簧。
生成组后,它会缓存在View上,并且可以使用AnimationKey触发,无论是否有动画。触发后,动画将相应地同步表示层的值,并相应地应用。
找到框架以下是一个例子:
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)