Вот что вам нужно знать, чтобы устранить мерцание:
Как указывает Калеб, Core Animation поворачивает ваш слой так, что его положительная ось X лежит вдоль касательной вашегодорожка.Вы должны заставить «естественную» ориентацию вашего изображения работать с этим.Итак, предположим, что это зеленый космический корабль в ваших примерах изображений, вам нужно, чтобы космический корабль указывал вправо, когда к нему не применено вращение.
Настройка преобразования, включающего вращениемешает вращению, применяемому kCAAnimationRotateAuto.Вы должны удалить вращение из вашего преобразования перед применением анимации.
Конечно, это означает, что вам нужно повторно применить преобразование после завершения анимации.И, конечно же, вы хотите сделать это, не видя мерцания при появлении изображения.Это не сложно, но здесь задействован какой-то секретный соус, который я объясню ниже.
Вы, вероятно, хотите, чтобы ваш космический корабль начал указывать вдоль касательной траектории, даже когда космический корабль сидитеще не будучи анимированным еще.Если изображение вашего космического корабля направлено вправо, но ваш путь идет вверх, то вам нужно настроить преобразование изображения так, чтобы оно включало поворот на 90 °.Но, возможно, вы не хотите жестко кодировать этот поворот - вместо этого вы хотите посмотреть на путь и выяснить его начальную касательную.
Я покажу здесь некоторые важные коды.Вы можете найти мой тестовый проект на github .Вы можете найти какую-то пользу в загрузке и испытании.Просто нажмите на зеленый «космический корабль», чтобы увидеть анимацию.
Итак, в моем тестовом проекте я подключил UIImageView
к действию с именем animate:
.Когда вы прикасаетесь к нему, изображение перемещается вдоль половины фигуры 8 и удваивается в размере.При повторном прикосновении изображение перемещается вдоль другой половины рисунка 8 (обратно в исходное положение) и возвращается к исходному размеру.Обе анимации используют kCAAnimationRotateAuto
, поэтому изображение указывает вдоль касательной к траектории.
Вот начало animate:
, где я выясняю, к какой траектории, масштабу и точке назначения должно прийти изображение:
- (IBAction)animate:(id)sender {
UIImageView* theImage = self.imageView;
UIBezierPath *path = _isReset ? _path0 : _path1;
CGFloat newScale = 3 - _currentScale;
CGPoint destination = [path currentPoint];
Итак, первое, что мне нужно сделать, это удалить любой поворот из преобразования изображения, поскольку, как я уже говорил, оно будет мешать kCAAnimationRotateAuto
:
// Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`.
theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale);
Затем я вхожу в блок анимации UIView
, чтобы система применила анимацию к представлению изображения:
[UIView animateWithDuration:3 animations:^{
Я создаю анимацию ключевого кадра для позиции и устанавливаю пару ее свойств:
// Prepare my own keypath animation for the layer position.
// The layer position is the same as the view center.
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.path = path.CGPath;
positionAnimation.rotationMode = kCAAnimationRotateAuto;
Следующий секретный соус для предотвращения мерцания в конце анимации.Напомним, что анимации не влияют на свойства «слоя модели», к которому вы их прикрепляете (в данном случае theImage.layer
).Вместо этого они обновляют свойства «уровня представления», который отражает то, что на самом деле отображается на экране.
Поэтому сначала я установил removedOnCompletion
на NO
для анимации ключевого кадра.Это означает, что анимация останется прикрепленной к слою модели, когда анимация будет завершена, что означает, что я могу получить доступ к слою презентации.Я получаю преобразование из слоя представления, удаляю анимацию и применяю преобразование к слою модели.Поскольку все это происходит в главном потоке, все эти изменения свойств происходят в одном цикле обновления экрана, поэтому мерцания не возникает.
positionAnimation.removedOnCompletion = NO;
[CATransaction setCompletionBlock:^{
CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform];
[theImage.layer removeAnimationForKey:positionAnimation.keyPath];
theImage.transform = finalTransform;
}];
Теперь, когда я настроил блок завершения, я действительно могу изменитьсвойства просмотра.Когда я это сделаю, система автоматически прикрепит анимацию к слою.
// UIView will add animations for both of these changes.
theImage.transform = CGAffineTransformMakeScale(newScale, newScale);
theImage.center = destination;
Я скопирую некоторые ключевые свойства из автоматически добавленной анимации позиции в мою анимацию ключевого кадра:
// Copy properties from UIView's animation.
CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath];
positionAnimation.duration = autoAnimation.duration;
positionAnimation.fillMode = autoAnimation.fillMode;
инаконец, я заменяю автоматически добавленную анимацию позиции на анимацию ключевого кадра:
// Replace UIView's animation with my animation.
[theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
}];
Дважды, наконец, я обновляю переменные своего экземпляра, чтобы отразить изменение в представлении изображения:
_currentScale = newScale;
_isReset = !_isReset;
}
Этоэто еили анимировать изображение без мерцания.
А теперь, как сказал бы Стив Джобс, «Одна последняя вещь».Когда я загружаю вид, мне нужно установить преобразование вида изображения так, чтобы оно вращалось так, чтобы указывать вдоль тангенса первого пути, который я буду использовать для его анимации.Я делаю это в методе с именем reset
:
- (void)reset {
self.imageView.center = _path1.currentPoint;
self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
_currentScale = 1;
_isReset = YES;
}
Конечно, хитрый бит скрыт в этой функции startRadiansForPath
.Это действительно не так сложно.Я использую функцию CGPathApply
для обработки элементов пути, выбирая первые две точки, которые фактически образуют подпуть, и вычисляю угол линии, образованной этими двумя точками.(Изогнутый участок пути представляет собой квадратичный или кубический сплайн Безье, и у этих сплайнов есть свойство, что касательная в первой точке сплайна - это линия от первой точки до следующей контрольной точки.)
Я просто собираюсь сбросить код здесь без объяснения причин для потомков:
typedef struct {
CGPoint p0;
CGPoint p1;
CGPoint firstPointOfCurrentSubpath;
CGPoint currentPoint;
BOOL p0p1AreSet : 1;
} PathState;
static inline void updateStateWithMoveElement(PathState *state, CGPathElement const *element) {
state->currentPoint = element->points[0];
state->firstPointOfCurrentSubpath = state->currentPoint;
}
static inline void updateStateWithPoints(PathState *state, CGPoint p1, CGPoint currentPoint) {
if (!state->p0p1AreSet) {
state->p0 = state->currentPoint;
state->p1 = p1;
state->p0p1AreSet = YES;
}
state->currentPoint = currentPoint;
}
static inline void updateStateWithPointsElement(PathState *state, CGPathElement const *element, int newCurrentPointIndex) {
updateStateWithPoints(state, element->points[0], element->points[newCurrentPointIndex]);
}
static void updateStateWithCloseElement(PathState *state, CGPathElement const *element) {
updateStateWithPoints(state, state->firstPointOfCurrentSubpath, state->firstPointOfCurrentSubpath);
}
static void updateState(void *info, CGPathElement const *element) {
PathState *state = info;
switch (element->type) {
case kCGPathElementMoveToPoint: return updateStateWithMoveElement(state, element);
case kCGPathElementAddLineToPoint: return updateStateWithPointsElement(state, element, 0);
case kCGPathElementAddQuadCurveToPoint: return updateStateWithPointsElement(state, element, 1);
case kCGPathElementAddCurveToPoint: return updateStateWithPointsElement(state, element, 2);
case kCGPathElementCloseSubpath: return updateStateWithCloseElement(state, element);
}
}
CGFloat startRadiansForPath(UIBezierPath *path) {
PathState state;
memset(&state, 0, sizeof state);
CGPathApply(path.CGPath, &state, updateState);
return atan2f(state.p1.y - state.p0.y, state.p1.x - state.p0.x);
}