Вот что вам нужно знать, чтобы устранить мерцание:
Как Калеб рода отметил, Core Animation поворачивает ваш слой так, что его положительная ось X лежит вдоль касательной вашего пути. Вам нужно сделать «естественную» ориентацию вашего изображения с этим. Итак, предположим, что это зеленый космический корабль в ваших образцах изображений, вам нужно, чтобы космический корабль указывал направо, когда на него не было поворота.
Установка преобразования, включающего поворот, препятствует вращению, применяемому `kCAAnimationRotateAuto '. Перед применением анимации необходимо удалить поворот из вашего преобразования.
Конечно, это означает, что вам нужно повторно применить преобразование, когда анимация завершена. И, конечно же, вы хотите сделать это, не видя какого-либо мерцания в образе изображения. Это не сложно, но там есть какой-то секретный соус, который я объясняю ниже.
Вы, по-видимому, хотите, чтобы ваш космический корабль начинал указывать вдоль касательной пути, даже когда космический корабль сидит еще не анимированным. Если изображение вашего космического корабля указывает вправо, но ваш путь идет вверх, вам необходимо установить преобразование изображения, чтобы оно включало поворот на 90 °. Но, возможно, вы не хотите жестко кодировать эту ротацию, вместо этого вы хотите посмотреть на путь и выяснить его начальную касательную.
Я покажу здесь некоторые важные коды. Вы можете найти my test project on 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];
}];
Double-finally Я обновляю свои переменные экземпляра, чтобы показать изменения к изображению:
_currentScale = newScale;
_isReset = !_isReset;
}
Это все, что нужно для анимации изображения без мерцания.
И теперь, как сказал Стив Джобс, «Одна последняя вещь». Когда я загружаю представление, мне нужно установить преобразование изображения, чтобы оно повернулось, чтобы указать вдоль касательной первого пути, который я буду использовать для его анимации. Я делаю это в методе с именем reset
:
- (void)reset {
self.imageView.center = _path1.currentPoint;
self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
_currentScale = 1;
_isReset = YES;
}
Конечно, сложно немного скрыт в этой startRadiansForPath
функции. Это действительно не так сложно. Я использую функцию CGPathApply
для обработки элементов пути, выбирая первые две точки, которые фактически образуют подпуть, и я вычисляю угол линии, образованной этими двумя точками. (Изогнутый участок пути представляет собой квадратичный или кубический сплайнер сплайна, и эти сплайны обладают тем свойством, что касательная в первой точке сплайна является линией от первой точки до следующей контрольной точки.)
I ' m просто собирается сбрасывать код здесь без объяснений, для потомков:
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);
}
Это очень помогло мне! Благодаря! – RyanG