119

我在iOS中很好地CALayer沿着(QuadCurve)制作动画。CGPath但我想使用比Apple提供的少数几个更有趣的缓动功能(EaseIn/EaseOut 等)。例如,反弹或弹性功能。

这些事情可以用 MediaTimingFunction (bezier) 来完成:

在此处输入图像描述

但我想创建更复杂的计时功能。问题是媒体时间似乎需要三次贝塞尔曲线,它不足以产生这些效果:

在此处输入图像描述
(来源: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(只是一个例子)。只需更换定时功能即可获得不同的效果。

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

我希望我在这里有意义。

4

6 回答 6

48

我找到了这个:

Cocoa with Love - Core Animation 中的参数加速曲线

但我认为使用块可以使它更简单、更易读。所以我们可以在 CAKeyframeAnimation 上定义一个看起来像这样的类别:

CAKeyframeAnimation+参数.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];
}

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

于 2011-05-11T01:51:15.147 回答
36

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

Duration 参数仍然存在于 Animator 中,但对于弹簧计时将被忽略。

如果这两个选项还不够,您还可以通过确认UITimingCurveProvider协议来实现自己的时序曲线。

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

另外,请参阅WWDC 2016 的 UIKit 动画和过渡演示的进展。

于 2016-11-01T15:48:29.870 回答
14

创建自定义计时函数的一种方法是使用 CAMediaTimingFunction 中的functionWithControlPoints::::工厂方法(也有相应的 initWithControlPoints:::: init 方法)。这样做是为您的计时功能创建一条贝塞尔曲线。它不是任意曲线,但贝塞尔曲线非常强大和灵活。掌握控制点需要一点练习。提示:大多数绘图程序都可以创建贝塞尔曲线。玩这些会给你一个关于你用控制点表示的曲线的视觉反馈。

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

编辑:以下代码显示了一个简单的弹跳动画。为此,我创建了一个组合计时函数(valuestiming NSArray 属性),并为动画的每个片段赋予不同的时间长度(keytimes属性)。通过这种方式,您可以组合 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];

}
于 2011-03-01T23:15:28.937 回答
10

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

于 2012-10-17T20:43:35.030 回答
2

一个快速版本的实现是TFAnimation。演示是一个正弦曲线动画。使用TFBasicAnimation就像CABasicAnimation除了分配以外timeFunction的块timingFunction

关键点是子类,并通过in s 间隔CAKeyframeAnimation计算帧位置。毕竟将所有计算值添加到of和时间间隔到太。timeFunction1 / 60fpsvaluesCAKeyframeAnimationkeyTimes

于 2016-06-06T01:33:57.947 回答
2

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

每个动画,每个属性,可以使用 33 条不同参数曲线中的 1 条、具有初始速度的衰减计时函数或根据您的需要配置的自定义弹簧。

生成组后,它会缓存在 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)
于 2016-06-23T07:17:11.377 回答