2012-02-27 2 views
11

Я экспериментирую с анимацией ключевого кадра положения объекта UIImageView, движущегося вдоль пути безье. Этот рисунок показывает начальное состояние перед анимацией. Синяя линия путь - сначала движется прямо вверх, светло-зеленый ящик начальный ограничивающий прямоугольник или изображение, и темно-зеленый «призрак» есть образ, что я двигаюсь:iOS CAKeyframeAnimation rotationMode on UIImageView

Initial state

Когда Я запускаю анимацию с параметром rotationMode, установленным на nil, изображение сохраняет ту же ориентацию на всем протяжении пути, как ожидалось.

Но когда я запускаю анимацию с поворотомMode, установленным на kCAAnimationRotateAuto, изображение сразу поворачивается на 90 градусов против часовой стрелки и сохраняет эту ориентацию на всем протяжении пути. Когда он достигнет конца пути он перерисовывает в правильной ориентации (ну это на самом деле показывает UIImageView, что я переориентироваться в конечном месте)

enter image description here

я наивно ожидал, что rotationMode бы сориентировать изображение на тангенс пути, а не нормальный, особенно когда документы компании Apple для CAKeyframeAnimation rotationMode состояния

определяет, будет ли анимировать объекты вдоль пути повернуть, чтобы соответствовать касательной к траектории.

Так в чем же тут решение? Должен ли я предварительно поворачивать изображение на 90 градусов по часовой стрелке? Или есть что-то, что мне не хватает?

Благодарим за помощь.


Редактировать 2 март

Я добавил шаг вращения перед путем анимации с помощью вращения Аффинного как:

theImage.transform = CGAffineTransformRotate(theImage.transform,90.0*M_PI/180); 

, а затем после пути анимации, сбрасывая вращение с:

theImage.transform = CGAffineTransformIdentity; 

Это делает e изображение следует по пути в ожидаемом порядке. Однако теперь я сталкиваюсь с другой проблемой мерцания изображения. Я уже ищу решение вопроса мерцающего в этом так вопросе:

iOS CAKeyFrameAnimation scaling flickers at animation end

Так что теперь я не знаю, если я сделал что-то лучше или хуже!


Edit 12 марта

Хотя Калеб указал, что да, я должен предварительно повернуть мое изображение, Роб представил удивительный пакет кода, который почти полностью решить мои проблемы. Единственное, что не делал Роб, это компенсация за то, что мои активы были нарисованы с вертикальной, а не горизонтальной ориентацией, тем самым все еще требуя предварительно обработать их на 90 градусов, прежде чем делать анимацию.Но эй, его единственная ярмарка, что я должен выполнить часть работы, чтобы добиться успеха.

Так что мои небольшие изменения решения Робы номера мои требований являются:

  1. Когда я добавить UIView, я предварительно Поверните его, чтобы противостоять присущее вращению добавляемого путем установки rotationMode:

    theImage.transform = CGAffineTransformMakeRotation (90 * M_PI/180.0);

  2. мне нужно держать, что вращение в конце анимации, так что вместо того, чтобы просто взрывая вид преобразует с новым коэффициентом масштабирования после того, как блок завершения определяется, я строй шкалы, основанная на текущей трансформации:

    theImage.transform = CGAffineTransformScale (theImage.transform, scaleFactor, scaleFactor);

И это все, что мне нужно было сделать, чтобы мое изображение следовало по пути, как я ожидал!


Редактировать 22 марта

Я только что загрузил на GitHub демо-проекта, который показывает перемещение объекта вдоль пути Безье. Код можно найти на PathMove

Я также писал об этом в своем блоге на Moving objects along a bezier path in iOS

+0

Это очень помогло мне! Благодаря! – RyanG

ответ

8

Проблема заключается в том, что авторотация Core Animation удерживает горизонтальную ось обзора параллельно касательной пути. Вот как это работает.

Если вы хотите, чтобы вертикальная ось вашего взгляда соответствовала тангенту пути, поворот содержимого представления, как вы сейчас делаете, - разумная вещь.

+0

OK .. так, как это работает. Тогда я положу это на проблему с документацией. Но когда я использую аффинное вращение, я сталкиваюсь с проблемой моего другого вопроса SO, упомянутого в приведенном выше правиле. Должен ли я использовать CA для предворота вместо вращения Affine? Или есть ли в этом другом вопросе, что я делаю неправильно? –

+0

Я никогда не тестировал, имеет ли значение использование CGAffineTransform, поэтому я не могу говорить об этом. Интуитивно, поскольку вы уже думаете о слоях с Core Animation, может иметь смысл использовать преобразование Core Animation вместо: '[theView.layer setValue: [NSNumber numberWithFloat: M_PI/2] forKey: @" transform.rotation " ]; '. Но также кажется, что CGAffineTransform должен быть реализован с использованием возможностей CALayer по трансформации, поэтому это может и не иметь значения. Я скоро нахожусь на месте, но посмотрю на ваш другой вопрос, когда у меня появится шанс. – Caleb

+0

Из ответа на мой другой вопрос я бы сказал, что поворот с анимацией ядра - это путь. Когда я получу твердый ответ на этот вопрос, я постараюсь также разрешить поворот таким же образом. –

3

Yow упомянуть, что вы пнуть анимацию с «rotationMode установлен в YES», но в документации говорится, что rotationMode должен быть установлен используя NSString ...

В частности:

Эти константы используются свойством rotationMode.

NSString * сопзЬ kCAAnimationRotateAuto

NSString * сопзЬ kCAAnimationRotateAutoReverse

Вы пробовали настройки:

keyframe.animationMode = kCAAnimationRotateAuto;

В документации говорится:

kCAAnimationRotateAuto: Объекты путешествия по касательной к траектории.

+0

Это был только текст вопроса, который был неправильным, я использую 'nil' и' kCAAnimationRotateAuto'. Просто отредактировал вопрос, чтобы это отразить. –

6

Вот что вам нужно знать, чтобы устранить мерцание:

  • Как Калеб рода отметил, 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); 
} 
+0

Спасибо за деталь этого ответа. Я очень ценю это. Однако из того, что я видел в вашем проекте, вы предварительно нарисовали актив, указываемый справа, повернув его на касательную траекторию на дисплее и удалив это вращение в анимации. В моей ситуации мне нужно использовать активы, которые эффективно предварительно нарисованы, поэтому они должны быть повернуты на 90 градусов, а затем перемещаться по пути. Я буду играть с вашим решением позже, чтобы увидеть, что я могу понять для себя сейчас, когда вы дали мне хорошую отправную точку. Еще раз спасибо. –

+0

Сегодня мне не потребовалось много времени, чтобы выяснить, что мне нужно сделать в соответствии с моими требованиями. Спасибо за то, что вы помогли мне! –

Смежные вопросы