Як створити спеціальну функцію полегшення за допомогою анімації 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.