Как создать пользовательскую функцию ослабления с помощью Core Animation?
Я неплохо анимирую CALayer
вдоль CGPath
(QuadCurve) в iOS. Но я бы хотел использовать более интересную функцию замедления, чем несколько , предоставляемых Apple (EaseIn/EaseOut и т.д.). Например, отказов или упругой функции.
Эти вещи можно сделать с помощью MediaTimingFunction (Безье):
![enter image description here]()
Но я хотел бы создать функции синхронизации, которые являются более сложными. Проблема заключается в том, что для синхронизации мультимедиа требуется кубический Безье, недостаточно мощный для создания этих эффектов:
![enter image description here]()
(источник: sparrow-framework.org)
Код code для создания вышеприведенного кода достаточно прост в других средах, что очень расстраивает. Обратите внимание, что кривые отображают время ввода на время вывода (кривая T-t), а не кривые временного положения. Например, easeOutBounce (T) = t возвращает новый t. Затем этот t используется для построения графика движения (или любого другого свойства, которое мы должны анимировать).
Итак, я хотел бы создать сложный кастом CAMediaTimingFunction
, но я понятия не имею, как это сделать, или если это вообще возможно? Есть ли альтернативы?
EDIT:
Вот конкретный пример шагов. Очень познавательно :)
Я хочу анимировать объект вдоль линии от точки a до b, но я хочу, чтобы он "отражал" свое движение вдоль линии, используя кривую easeOutBounce выше. Это означает, что он будет следовать точной линии от a до b, но будет ускоряться и замедляться более сложным способом, чем это возможно при использовании текущей CAMediaTimingFunction на основе Безье.
Давайте сделаем эту линию любым произвольным движением кривой, заданным с помощью CGPath. Он все еще должен двигаться вдоль этой кривой, но он должен ускоряться и замедляться так же, как в примере с линией.
Теоретически я думаю, что это должно работать так:
Давайте опишем кривую движения как анимацию ключевого кадра move (t) = p, где t - время [0..1], p - позиция, рассчитанная в момент времени t. Таким образом, move (0) возвращает позицию в начале кривой, перемещает (0.5) точную середину и Move (1) в конце. Использование функции времени time (T) = t для предоставления значений t для перемещения должно дать мне то, что я хочу. Для эффекта отскакивания функция хронирования должна возвращать одинаковые значения t для времени (0,8) и времени (0,8) (только пример). Просто замените функцию синхронизации, чтобы получить другой эффект.
(Да, можно выполнять отскок строк, создавая и соединяя четыре отрезка линии, которые перемещаются назад и вперед, но в этом нет необходимости. В конце концов, это просто простая линейная функция, которая отображает значения времени в позиции.)
Я надеюсь, что здесь есть смысл.
Ответы
Ответ 1
Я нашел это:
Cocoa с любовью - кривые параметрического ускорения в 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];
}
Я знаю, что это может быть не так просто, как вы хотели, но это начало.
Ответ 2
Из 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 позволяет разработчикам манипулировать отношением демпфирования, массой, жесткостью и начальной скоростью для создания желаемого поведения spring.
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, но будет игнорироваться для spring синхронизации.
Если этих двух параметров недостаточно, вы также можете реализовать свою собственную временную кривую, подтвердив протокол UITimingCurveProvider.
Подробнее о том, как создавать анимации с разными временными параметрами, можно найти в документации.
Также, пожалуйста, см. Достижения в презентации UIKit Animations and Transitions от WWDC 2016.
Ответ 3
Для создания пользовательской функции синхронизации используется метод functionWithControlPoints:: factory в CAMediaTimingFunction (также есть соответствующий метод 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];
}
Ответ 4
Не уверен, что вы все еще ищете, но PRTween выглядит довольно впечатляюще с точки зрения его способности выйти за рамки того, что дает Core Animation вы из коробки, в первую очередь, пользовательские функции синхронизации. Он также поставляется в комплекте со многими, если не всеми, популярными кривыми ослабления, которые предоставляют различные веб-фреймворки.
Ответ 5
Я взял ответ Джесси Кроссен и немного расширил его. Вы можете использовать его для анимации CGPoints и CGSize, например. Начиная с iOS 7, вы также можете использовать произвольные функции времени с анимацией UIView.
Вы можете проверить результаты на https://github.com/jjackson26/JMJParametricAnimation
Ответ 6
Быстрая реализация версии TFAnimation. Демонстрация - это анимация кривой sin. Используйте TFBasicAnimation
точно так же, как CABasicAnimation
кроме присваивания timeFunction
блоком, отличным от timingFunction
.
Ключевым моментом является подкласс CAKeyframeAnimation
и вычисляет положение кадра на timeFunction
в интервале 1 / 60fps
. После того, как все добавьте все вычисленное значение в values
of CAKeyframeAnimation
и время от интервала до keyTimes
тоже.
Ответ 7
Я создал подход на основе блоков, который генерирует группу анимации с несколькими анимациями.
Каждая анимация за каждое свойство может использовать 1 из 33 различных параметрических кривых, функцию синхронизации распада с начальной скоростью или пользовательский spring, который будет настроен на ваши потребности.
Как только группа сгенерирована, она кэшируется в представлении и может запускаться с помощью 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)