Как создать собственную функцию замедления с помощью Core Animation? - PullRequest
109 голосов
/ 02 марта 2011

Я анимирую CALayer вдоль CGPath (QuadCurve) в iOS.Но я бы хотел использовать более интересную функцию замедления, чем несколько , предоставленных Apple (EaseIn / EaseOut и т. Д.).Например, отказов или упругой функции.

Эти функции можно сделать с помощью MediaTimingFunction (Безье):

enter image description here

Но я хотел бы создать функции синхронизации , которыеболее сложный.Проблема заключается в том, что для синхронизации мультимедиа необходим кубический Безье, который недостаточно силен для создания этих эффектов:

http://wiki.sparrow -framework.org / _media / manual / transitions.png

Код для создания вышеупомянутого достаточно прост в других средах, что очень расстраивает.Обратите внимание, что кривые отображают время ввода на время вывода (кривая Tt), а не кривые временного положения.Например, easeOutBounce (T) = t возвращает новый t .Затем это t используется для построения графика движения (или любого другого свойства, которое мы должны анимировать).

Итак, я хотел бы создать сложный пользовательский CAMediaTimingFunction, но у меня естьПонятия не имею, как это сделать, или если это вообще возможно?Есть ли альтернативы?

РЕДАКТИРОВАТЬ:

Вот конкретный пример для шагов.Очень познавательно:)

  1. Я хочу анимировать объект вдоль линии от точки a до b , но я хочу, чтобы он "отскочил""его движение вдоль линии с использованием кривой easeOutBounce выше.Это означает, что он будет следовать точной линии от a до b , но будет ускоряться и замедляться более сложным способом, чем это возможно при использовании текущей CAMediaTimingFunction на основе Безье.

  2. Позволяет сделать эту линию произвольным движением кривой, заданным с помощью CGPath.Он все еще должен двигаться вдоль этой кривой, но он должен ускоряться и замедляться так же, как в примере с линией.

Теоретически я думаю, что это должно работать так:

Позволяет описать кривую движения как анимацию ключевого кадра move (t) = p , где t - время [0..1], p - вычисленная позицияв момент времени т .Таким образом, move (0) возвращает позицию в начале кривой, move (0.5) точную середину и move (1) в конце.Использование функции синхронизации time (T) = t для предоставления значений t для move должно дать мне то, что я хочу.Для эффекта отскока функция хронирования должна возвращать те же самые значения t для времени (0,8) и времени (0,8) (только пример).Просто замените функцию хронометража, чтобы получить другой эффект.

(Да, можно выполнять отскок строк, создавая и соединяя четыре отрезка, которые идут вперед и назад, но в этом нет необходимости. В конце концов, это просто простая линейная функция, которая отображает time значения в позиции.)

Надеюсь, что здесь есть смысл.

Ответы [ 7 ]

47 голосов
/ 11 мая 2011

Я нашел это:

Какао с любовью - Параметрические кривые ускорения в 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];
}

Я знаю, что это может быть не так просто, как вы хотели, но это начало.

27 голосов
/ 01 ноября 2016

В 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 .

Подробнее о том, как создавать анимацию с различными параметрами синхронизации, вы можете найти в документации .

Также, пожалуйста, смотрите Достижения в UIKit Анимации и переходы презентация от WWDC 2016.

13 голосов
/ 02 марта 2011

Способ создания пользовательской функции синхронизации заключается в использовании фабричного метода functionWithControlPoints :::: в CAMediaTimingFunction (также есть соответствующий метод initWithControlPoints :::: init). Это создает кривую Безье для вашей функции синхронизации. Это не произвольная кривая, но кривые Безье очень мощные и гибкие. Требуется немного практики, чтобы овладеть контрольными точками. Совет: большинство программ для рисования могут создавать кривые Безье. Играя с ними, вы получите визуальную обратную связь с кривой, которую вы представляете с контрольными точками.

эта ссылка указывает на документацию Apple. Существует короткий, но полезный раздел о том, как функции предварительной сборки строятся из кривых.

Edit: Следующий код показывает простую анимацию отказов. Для этого я создал составную функцию хронирования ( значения и хронометраж свойства 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];

}
9 голосов
/ 18 октября 2012

Не уверен, что вы все еще ищете, но PRTween выглядит довольно впечатляюще с точки зрения способности выходить за рамки того, что Core Animation предоставляет вам из коробки, в частности, с пользовательскими функциями синхронизации. Он также поставляется со многими - если не со всеми - популярными кривыми замедления, которые предоставляют различные веб-фреймворки.

4 голосов
/ 10 января 2014

Я взял ответ Джесси Кроссена и немного расширил его.Вы можете использовать его для анимации CGPoints и CGSize, например.Начиная с iOS 7, вы также можете использовать произвольные функции времени с анимацией UIView.

Вы можете проверить результаты в https://github.com/jjackson26/JMJParametricAnimation

1 голос
/ 23 июня 2016

Я создал подход, основанный на блоках, который генерирует группу анимации с несколькими анимациями.

В каждой анимации для каждого свойства может использоваться 1 из 33 различных параметрических кривых, функция времени затухания с начальной скоростью или настраиваемая пружина, настроенная в соответствии с вашими потребностями.

Как только группа сгенерирована, она кэшируется в представлении и может быть запущена с помощью 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)
1 голос
/ 06 июня 2016

Реализация быстрой версии: TFAnimation . Демонстрация - это sin анимация кривой. Используйте TFBasicAnimation так же, как CABasicAnimation, за исключением назначения timeFunction с блоком, отличным от timingFunction.

Ключевым моментом является подкласс CAKeyframeAnimation и вычисление положения кадров на timeFunction с интервалом 1 / 60fps с. В конце концов добавьте все вычисленные значения к values из CAKeyframeAnimation и временипо интервалу до keyTimes тоже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...