Як створити спеціальну функцію полегшення за допомогою анімації Core?


115

Я анімую CALayerразом CGPath(QuadCurve) досить добре в iOS. Але я хотів би скористатися цікавішою функцією полегшення, ніж ті, які надає Apple (EaseIn / EaseOut тощо). Наприклад, функція відскоку або пружність.

Ці речі можна зробити з MediaTimingFunction (безьє):

введіть тут опис зображення

Але я хотів би створити функції часу, які є більш складними. Проблема полягає в тому, що, здається, для медіа-таймінгу потрібен кубічний безьє, який не є достатньо потужним для створення цих ефектів:

введіть тут опис зображення
(джерело: sparrow-framework.org )

Код , щоб створити розглянутому вище , є досить простим в інших рамках, що робить це дуже засмучує. Зверніть увагу, що криві відображають вхідний час на час виходу (крива Тт), а не криві часового положення. Наприклад, easyOutBounce (T) = t повертає нову t . Потім це t використовується для побудови руху (або будь-якого властивості, яке ми мусимо оживити).

Отже, я хотів би створити складний звичай, CAMediaTimingFunctionале я не маю поняття, як це зробити, або якщо це навіть можливо? Чи є альтернативи?

Редагувати:

Ось конкретний приклад кроків. Дуже освітянський :)

  1. Я хочу , щоб оживити об'єкт уздовж лінії від точки А до б , але я хочу його «відскік» свій рух уздовж лінії , використовуючи криву easeOutBounce вище. Це означає, що він буде слідувати точній лінії від a до b , але прискориться і сповільниться більш складним способом, ніж це можливо, використовуючи поточний CAMediaTimingFunction на основі безьє.

  2. Дозволяє зробити цю лінію будь-яким довільним рухом кривої, визначеним CGPath. Він все одно повинен рухатися по цій кривій, але він повинен прискорюватися і зменшуватися так само, як у прикладі лінії.

Теоретично я думаю, що це має працювати так:

Давайте опишемо криву руху як рух анімації ключових кадрів (t) = p , де t - час [0..1], p - положення, обчислене в момент t . Отже, переміщення (0) повертає положення на початку кривої, переміщуємо (0,5) точну середину і рухаємо (1) в кінці. Використання функції функціонування часу (T) = t для надання значень t для переміщення має дати мені те, що я хочу. Для підстрибуючи ефекту, функція часу повинна повертати одні і ті ж т значення часу (0.8) і часу (0.8)(лише приклад). Просто замініть функцію синхронізації, щоб отримати інший ефект.

(Так, можна робити підстрибування рядків, створюючи та з'єднуючись чотири рядкові сегменти, які йдуть вперед і назад, але це не повинно бути необхідним. Зрештою, це просто проста лінійна функція, яка відображає значення часу на позиції.)

Я сподіваюся, що я маю сенс тут.


майте на увазі, що (як це часто буває у ЗП), на це дуже давнє запитання зараз є дуже застарілі відповіді .. не забудьте прокрутити вниз до дивовижних поточних відповідей. : (Дуже примітний cubic-bezier.com !)
Fattie

Відповіді:


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
Я взяв відповідь Джессі Кроссена і трохи розширив її. Ви можете використовувати його для анімації CGPoints, наприклад, CGSize. Як і в iOS 7, ви також можете використовувати довільні функції часу з анімацією UIView. Ви можете ознайомитись з результатами на сайті github.com/jjackson26/JMJParametricAnimation
JJ Jackson

@JJJackson Чудова колекція анімацій! Дякуємо, що
зібрали

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 .

Більш детально, як створювати анімації з різними параметрами часу, ви можете дізнатися в документації .

Також, будь ласка, дивіться презентацію авансів у анімації та переходах UIKit від WWDC 2016.


Ви можете знайти приклади використання функцій синхронізації в iOS10-анімації-демо-сховищі .
Ольга Конорева

Чи можете ви, будь ласка, детальніше розповісти про "Якщо цих двох варіантів недостатньо, ви також можете реалізувати власну криву часу, підтвердивши протокол UITimingCurveProvider." ?
Крістіан Шнорр

Дивовижно! А CoreAnimationверсія UISpringTimingParameters( CASpringAnimation) підтримується назад до iOS 9!
Ритмічний фістман

@OlgaKonoreva, сервіс cubic-bezier.com - ВІДСТАВНА ЦІНА І ЧУДОВИЙ . Сподіваюся , ви стіл користувачем SO - відправляти вам жир дупу щедрот сказати спасибі :)
Fattie

@ChristianSchnorr Я думаю, що відповідь ухиляється від здатності a UITimingCurveProviderне тільки представляти кубічну або весняну анімацію, але й представляти анімацію, що поєднує в собі як кубічний, так і весняний via UITimingCurveType.composed.
Девід Джеймс

14

Спосіб створення спеціальної функції синхронізації - це використання функціїWWCoCorolLoints :::: заводський метод у CAMediaTimingFunction (є також відповідний метод initWithControlPoints :::: init). Для цього потрібно створити криву Безьє для функції синхронізації. Це не довільна крива, але криві Безьє дуже потужні та гнучкі. Потрібно небагато практики, щоб отримати вішання контрольних точок. Порада: більшість програм малювання можуть створювати криві Безьє. Граючи з тими, ви отримаєте візуальний відгук про криву, яку ви представляєте, з контрольними точками.

Це посилання вказує на документацію Apple. Існує короткий, але корисний розділ про те, як функції попереднього побудови побудовані з кривих.

Редагувати: Наступний код показує просту анімацію відмов. Для цього я створив складену функцію синхронізації ( значення та властивості 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];

}

Так це правда. Ви можете комбінувати декілька функцій синхронізації, використовуючи значення та властивості timingFunctions CAKeyframeAnimation, щоб досягти бажаного. Я опрацюю приклад і трохи вкладу код.
fsaint

Дякую за ваші зусилля та бали, що намагаєтесь :) Такий підхід може працювати теоретично, але його потрібно налаштувати для кожного використання. Я шукаю загальне рішення, яке працює для всіх анімацій як функція синхронізації. Якщо чесно, це виглядає не так добре, з’єднуючи лінії і криві разом. Приклад "домкрат у коробці" також страждає від цього.
Мартін Вікман

1
Ви можете вказати CGPathRef замість масиву значень для анімації ключових кадрів, але врешті-решт Felz правильний - CAKeyframeAnimation та timingFunctions дадуть вам все необхідне. Я не впевнений, чому ви вважаєте, що це не "загальне рішення", яке повинно бути "налаштоване під кожне використання". Ви збираєте анімацію ключових кадрів один раз фабричним методом або щось таке, а потім можете додавати цю анімацію в будь-який шар знову і знову стільки разів, скільки вам потрібно. Чому це потрібно підганяти під кожне використання? Мені здається, це нічим не відрізняється від вашого уявлення про те, як повинні функціонувати функції синхронізації.
Метт Лонг

1
О, хлопці, зараз у них запізнення на 4 роки, але якщо функціяWithControlPoints :::: цілком може робити анімацію / пружини. Використовуйте його спільно з cubic-bezier.com/#.6,1.87,.63,.74
Метт Фолі

10

Не впевнений, що ви все ще шукаєте, але PRTween виглядає досить вражаючою з точки зору своєї здатності вийти за рамки того, що Core Animation дає вам поза коробкою, особливо це - спеціальні функції синхронізації. Він також постачається з багатьма (якщо не всіма) популярними кривими ослаблення, які надають різні веб-рамки.


2

Швидка реалізація версії - це TFAnimation . Демонстрація - це анімація кривої гріха . ВикористовуйтеTFBasicAnimation так само, CABasicAnimationкрім присвоєння timeFunctionіншому блоку, ніж timingFunction.

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


2

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

Кожна анімація на власність може використовувати 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.