如何使用Core Animation创建自定义缓动功能?


115

我在iOS中很好地CALayerCGPath(QuadCurve)设置了动画。但是我想使用比Apple 提供的少数几个有趣的缓动功能(EaseIn / EaseOut等)。例如,反弹功能或弹性功能。

这些事情可能与MediaTimingFunction(贝塞尔曲线)有关:

在此处输入图片说明

但是我想创建更复杂的计时功能。问题在于媒体计时似乎需要一个三次方贝塞尔曲线,而这个贝塞尔曲线不够强大,无法产生以下效果:

在此处输入图片说明
(来源:sparrow-framework.org

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

因此,我想创建一个复杂的自定义,CAMediaTimingFunction但我不知道如何执行此操作,或者是否有可能?有其他选择吗?

编辑:

这是步骤的具体示例。很有教育意义:)

  1. 我想沿着从点ab的直线为对象设置动画,但是我希望它使用上面的easeOutBounce曲线沿其直线“反弹”其运动。这意味着它将遵循从ab的精确线,但是将比使用当前基于贝塞尔的CAMediaTimingFunction可能的方式更加复杂地加速和减速。

  2. 让该线成为CGPath指定的任意曲线移动。它仍应沿该曲线移动,但应与直线示例相同的方式加速和减速。

从理论上讲,我认为它应该像这样工作:

让我们将运动曲线描述为关键帧动画move(t)= p,其中t是时间[0..1],p是在时间t计算的位置。因此,move(0)返回曲线起点的位置,move(0.5)精确地到达中间,而move(1)结束。使用时间函数time(T)= t提供t移动值应该给我我想要的。为了产生弹跳效果,计时功能应针对time(0.8)time(0.8)返回相同的t(只是一个例子)。只需替换计时功能即可获得不同的效果。

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

我希望我在这里有意义。


请注意(就像在SO中通常这样),这个非常老的问题现在有了非常过时的答案 ..确保向下滚动到令人惊奇的当前答案。(高度显着cubic-bezier.com!)
Fattie

Answers:


48

我找到了这个:

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

但是我认为可以通过使用块使其变得更简单,更易读。因此,我们可以在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
我接受了Jesse Crossen的回应,并对此进行了扩展。您可以使用它来设置CGPoints和CGSize的动画。从iOS 7开始,您还可以将任意时间函数与UIView动画一起使用。您可以在github.com/jjackson26/JMJParametricAnimation中
Jackson

@JJJackson很棒的动画收藏!感谢您收集它们
Codey

33

从iOS 10开始,使用两个新的计时对象可以更轻松地创建自定义计时功能。

1)UICubicTimingParameters允许将三次贝塞尔曲线定义为缓动函数。

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)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协议来实现自己的时序曲线。

有关更多详细信息,以及如何创建具有不同定时参数的动画,请参见文档

另外,请参阅WWDC 2016的UIKit动画和过渡效果的高级介绍


您可以在iOS10-animations-demo存储库中找到使用Timing函数的示例
奥尔加·科诺雷娃

您能否详细说明“如果这两个选项还不够,您还可以通过确认UITimingCurveProvider协议来实现自己的时序曲线。” ?
Christian Schnorr

太棒了!而且()CoreAnimation版本在iOS 9之前受支持!UISpringTimingParametersCASpringAnimation
节奏拳头

@OlgaKonoreva,cubic-bezier.com的服务非常便宜而且很棒。希望您仍然是SO用户-向您发送丰厚的赏金以表示感谢:)
Fattie

@ChristianSchnorr我认为答案UITimingCurveProvider不在于a的能力,它不仅可以表示立方体弹簧动画,而且还可以代表组合了cubic spring via 的动画UITimingCurveType.composed
大卫·詹姆斯

14

创建自定义计时功能的一种方法是使用CAMediaTimingFunction中的functionWithControlPoints ::::工厂方法(也有相应的initWithControlPoints :::: init方法)。这是为您的计时功能创建贝塞尔曲线。它不是任意曲线,但贝塞尔曲线非常有力且灵活。掌握一些控制点需要一些实践。提示:大多数绘图程序都可以创建贝塞尔曲线。进行这些操作将使您对用控制点表示的曲线产生视觉反馈。

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

编辑:以下代码显示了一个简单的反弹动画。为此,我创建了一个组合的计时函数(计时 NSArray属性),并为动画的每个片段赋予了不同的时间长度(keytimes属性)。这样,您可以合成贝塞尔曲线以为动画合成更复杂的时序。是一篇有关此类动画的好文章,并提供了不错的示例代码。

- (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的value和TimingFunctions属性组合多个计时函数,以实现所需的功能。我将给出一个示例,然后将代码放一点。
fsaint'3

感谢您的努力,并感谢您尝试的要点:)这种方法在理论上可能有效,但是需要针对每种用途进行定制。我正在寻找一种适用于所有动画的定时解决方案的通用解决方案。老实说,看起来并没有很好的拼接线和曲线。“装箱起重器”示例也遭受此困扰。
马丁·威克曼

1
您可以为关键帧动画指定CGPathRef而不是值数组,但最终Felz是正确的-CAKeyframeAnimation和TimingFunctions将为您提供所需的一切。我不确定为什么您认为这不是必须“针对每种用途进行定制”的“通用解决方案”。您可以使用工厂方法或某种方法构建一次关键帧动画,然后可以根据需要多次重复地将该动画添加到任何图层。为什么每次使用都需要定制?在我看来,这与您对计时功能应如何工作的看法没有什么不同。
马特·朗

1
哦,伙计们,现在已经迟了4年了,但是如果functionWithControlPoints :::::可以完全制作出有弹性/弹性的动画。与cubic-bezier.com/#.6,1.87,.63,.74
Matt Foley

10

不确定您是否还在寻找,但PRTween超越了Core Animation为您提供的功能,尤其是自定义计时功能,在其功能方面看起来令人印象深刻。它还包含了各种Web框架提供的许多(如果不是全部)流行的缓动曲线。


2

一个迅速版本实现TFAnimation .The演示是一种罪过曲线animation.Use TFBasicAnimation一样CABasicAnimation,除了分配timeFunction比其他块timingFunction

关键是子类,CAKeyframeAnimationtimeFunction1 / 60fpss为间隔计算帧位置。毕竟还要将所有计算出的值添加到valuesof中CAKeyframeAnimation,将所有时间也添加到间隔中keyTimes


2

我创建了一种基于块的方法,该方法可以生成具有多个动画的动画组。

每个属性的每个动画可以使用33种不同的参数曲线之一,具有初始速度的“衰减”计时功能或根据您的需要配置的自定义弹簧。

生成组后,将其缓存在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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.