2010-03-07 2 views
13

Я пытаюсь реализовать флип-анимацию, которая будет использоваться в настольной игре, например iPhone-приложении. Ожидается, что анимация будет похожа на фрагмент игры, который вращается и изменяется на цвет его спины (вроде как Reversi piece). Мне удалось создать анимацию, которая переворачивает фигуру вокруг своей ортогональной оси, но когда я пытаюсь перевернуть ее вокруг диагональной оси, изменяя вращение вокруг оси z, фактическое изображение также поворачивается (не удивительно). Вместо этого я хотел бы повернуть изображение «как есть» вокруг диагональной оси.Как поворачивать CALayer вокруг диагональной линии?

Я попытался сменить layer.sublayerTransform, но безуспешно.

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

Окончательная версия: Основано на предположении Лоренцоса о создании дискретной ключевой анимации и вычислении матрицы преобразования для каждого кадра. Эта версия вместо этого пытается оценить количество «направляющих» кадров, необходимых на основе размера слоя, а затем использует анимацию с линейной привязкой. Эта версия вращается с произвольным углом, поэтому для поворота вокруг диагональной линии используйте угол 45 градусов.

Пример использования:

[someclass flipLayer:layer image:image angle:M_PI/4] 

Реализация:

- (void)animationDidStop:(CAAnimationGroup *)animation 
       finished:(BOOL)finished { 
    CALayer *layer = [animation valueForKey:@"layer"]; 

    if([[animation valueForKey:@"name"] isEqual:@"fadeAnimation"]) { 
    /* code for another animation */ 
    } else if([[animation valueForKey:@"name"] isEqual:@"flipAnimation"]) { 
    layer.contents = [animation valueForKey:@"image"]; 
    } 

    [layer removeAllAnimations]; 
} 

- (void)flipLayer:(CALayer *)layer 
      image:(CGImageRef)image 
      angle:(float)angle { 
    const float duration = 0.5f; 

    CAKeyframeAnimation *rotate = [CAKeyframeAnimation 
           animationWithKeyPath:@"transform"]; 
    NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease]; 
    NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease]; 
    /* bigger layers need more "guiding" values */ 
    int frames = MAX(layer.bounds.size.width, layer.bounds.size.height)/2; 
    int i; 
    for (i = 0; i < frames; i++) { 
    /* create a scale value going from 1.0 to 0.1 to 1.0 */ 
    float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1); 

    CGAffineTransform t1, t2, t3; 
    t1 = CGAffineTransformMakeRotation(angle); 
    t2 = CGAffineTransformScale(t1, scale, 1.0f); 
    t3 = CGAffineTransformRotate(t2, -angle); 
    CATransform3D trans = CATransform3DMakeAffineTransform(t3); 

    [values addObject:[NSValue valueWithCATransform3D:trans]]; 
    [times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]]; 
    } 
    rotate.values = values; 
    rotate.keyTimes = times; 
    rotate.duration = duration; 
    rotate.calculationMode = kCAAnimationLinear; 

    CAKeyframeAnimation *replace = [CAKeyframeAnimation 
            animationWithKeyPath:@"contents"]; 
    replace.duration = duration/2; 
    replace.beginTime = duration/2; 
    replace.values = [NSArray arrayWithObjects:(id)image, nil]; 
    replace.keyTimes = [NSArray arrayWithObjects: 
         [NSNumber numberWithDouble:0.0f], nil]; 
    replace.calculationMode = kCAAnimationDiscrete; 

    CAAnimationGroup *group = [CAAnimationGroup animation]; 
    group.duration = duration; 
    group.timingFunction = [CAMediaTimingFunction 
          functionWithName:kCAMediaTimingFunctionLinear]; 
    group.animations = [NSArray arrayWithObjects:rotate, replace, nil]; 
    group.delegate = self; 
    group.removedOnCompletion = NO; 
    group.fillMode = kCAFillModeForwards; 
    [group setValue:@"flipAnimation" forKey:@"name"]; 
    [group setValue:layer forKey:@"layer"]; 
    [group setValue:(id)image forKey:@"image"]; 

    [layer addAnimation:group forKey:nil]; 
} 

Оригинальный код:

+ (void)flipLayer:(CALayer *)layer 
      toImage:(CGImageRef)image 
     withAngle:(double)angle { 
    const float duration = 0.5f; 

    CAKeyframeAnimation *diag = [CAKeyframeAnimation 
           animationWithKeyPath:@"transform.rotation.z"]; 
    diag.duration = duration; 
    diag.values = [NSArray arrayWithObjects: 
       [NSNumber numberWithDouble:angle], 
       [NSNumber numberWithDouble:0.0f], 
       nil]; 
    diag.keyTimes = [NSArray arrayWithObjects: 
        [NSNumber numberWithDouble:0.0f], 
        [NSNumber numberWithDouble:1.0f], 
        nil]; 
    diag.calculationMode = kCAAnimationDiscrete; 

    CAKeyframeAnimation *flip = [CAKeyframeAnimation 
           animationWithKeyPath:@"transform.rotation.y"]; 
    flip.duration = duration; 
    flip.values = [NSArray arrayWithObjects: 
       [NSNumber numberWithDouble:0.0f], 
       [NSNumber numberWithDouble:M_PI/2], 
       [NSNumber numberWithDouble:0.0f], 
       nil]; 
    flip.keyTimes = [NSArray arrayWithObjects: 
        [NSNumber numberWithDouble:0.0f], 
        [NSNumber numberWithDouble:0.5f], 
        [NSNumber numberWithDouble:1.0f], 
        nil]; 
    flip.calculationMode = kCAAnimationLinear; 

    CAKeyframeAnimation *replace = [CAKeyframeAnimation 
            animationWithKeyPath:@"contents"]; 
    replace.duration = duration/2; 
    replace.beginTime = duration/2; 
    replace.values = [NSArray arrayWithObjects:(id)image, nil]; 
    replace.keyTimes = [NSArray arrayWithObjects: 
         [NSNumber numberWithDouble:0.0f], nil]; 
    replace.calculationMode = kCAAnimationDiscrete; 

    CAAnimationGroup *group = [CAAnimationGroup animation]; 
    group.removedOnCompletion = NO; 
    group.duration = duration; 
    group.timingFunction = [CAMediaTimingFunction 
          functionWithName:kCAMediaTimingFunctionLinear]; 
    group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil]; 
    group.fillMode = kCAFillModeForwards; 

    [layer addAnimation:group forKey:nil]; 
} 
+0

Что такое диагональная ось? – kennytm

+0

По оси диагонали я имею в виду, что вращение должно быть вокруг диагонали слоя. Например, если вы схватили два диагональных угла слоя и вращали их вокруг. –

+2

Как, поверните вокруг прямой 'y = x'? – kennytm

ответ

6

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

A-----B   B 
|  |  /
|  | -> A&D 
|  |  /
C-----D  C 

изменить изображение и преобразовать CALayer обратно в другую анимацию. Это создаст иллюзию слоя, вращающегося вокруг его диагонали.

матрица для этого должно быть, если я помню математику правильно:

0.5 0.5 0 
0.5 0.5 0 
0 0 1 

Обновление: нормально, CA doen't действительно любит использовать вырожденные преобразования, но вы можете приблизить это следующим образом:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f); 
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f); 
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f); 

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

После дополнительных исследований выяснилось, что нужно активировать собственный переход через ключевые кадры, чтобы обеспечить максимальный контроль самого перехода.скажем, вы должны были отобразить эту анимацию за секунду, вы должны сделать десять матриц, которые будут отображаться на каждой десятой доли секунды с помощью интерполяции с использованием kCAAnimationDiscrete; те, матрицы могут быть получены с помощью кода ниже:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f); 
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f); 
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f); 

где animationStepValue для ECH ключевого кадра берется из этой прогрессии:

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1} 

, который: вы генерирующие десять различной матрицы преобразования (на самом деле 9), нажимая их как ключевые кадры, которые должны отображаться на каждую десятую секунды, а затем используя параметр «не интерполировать». вы можете настроить анимационный номер для балансировки гладкости и производительности *

* Извините за возможные ошибки, эта последняя часть была написана без проверки орфографии.

+0

Спасибо за ваш ответ. Предполагается ли, что ASCII-искусство показывает преобразование сдвига? если я пытаюсь анимировать от личности, то к вашей матрице и их обратно к идентификатору я не получаю никакого изображения :( Я посмотрел, как сделать матрицу сдвига, и это выглядит так: {{1, sheary, 0} , {shearx, 1, 0}, {0, 0, 1}} Если я использую это в анимации, он работает, чтобы немного поработать, но мне нужно очень сильно сдвинуть, а затем изображение также будет уменьшено. –

+0

Никакой сдвиг не сделает параллелограмм, здесь мы пытаемся получить вырожденный ромб, при этом угол A & D перемещается к центру, а не к углам A & B, движущимся влево. Мне придется попробовать это на устройстве завтра, чтобы получить фактические параметры матрицы –

+0

Хорошо. Я читал, как действительно действуют аффинные преобразования, и преобразование, которое вы описываете с помощью диаграммы, должно, как вы говорите, быть аффинным, сохраняя точки на линии и крысе ios (до тех пор, пока A и D являются «противоположностями», я думаю?). Но я действительно не понял, как думать о числах в матрице. –

4

Я решил его решить. У вас, вероятно, уже есть решение, но вот что я нашел. Это довольно просто ...

Вы можете использовать CABasicAnimation для вращения по диагонали, но это должна быть конкатенация двух матриц, а именно существующей матрицы слоя, плюс CATransform3DRotate. «Трюк» в 3DRotate вам нужно указать координаты для вращения.

код выглядит примерно так:


CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0)); 

Это сделает поворот, который появляется, как будто верхний левый угол квадрата вращается вокруг оси Y = X, и бегущую нижне- правый угол.

Код для анимации выглядит следующим образом:


CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:@"transform"]; 

// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion 
[ani1 setDelegate:self]; 

// set the duration of the animation - a float 
[ani1 setDuration:dur]; 

// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors) 
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))]; 

// give the animation a name so you can check it in the animationDidStop:finished: method 
[ani1 setValue:@"shrink" forKey:@"name"]; 

// finally, apply the animation 
[theLayer addAnimation:ani1 [email protected]"arbitraryKey"]; 

Вот оно! Этот код будет вращать квадрат (theLayer) до невидимости, когда он перемещается на 90 градусов и представляет собой ортогонально на экран. Затем вы можете изменить цвет и сделать ту же самую анимацию, чтобы вернуть ее обратно. Такая же анимация работает, потому что мы конкатенируем матрицы, поэтому каждый раз, когда вы хотите повернуть, просто сделайте это дважды, или измените M_PI/2 на M_PI.

И, наконец, следует отметить, что это повлекло за собой гайки, которые по завершении слоя возвращаются в исходное состояние, если вы явно не установите его в состояние конечной анимации. Это означает, что как раз перед линией [theLayer addAnimation:ani1 [email protected]"arbitraryKey"]; вы хотите добавить


theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0)); 

установить его значение после того, как анимация завершается. Это предотвратит возврат привязки в исходное состояние.

Надеюсь, это поможет. Если не ты, то, возможно, кто-то другой, который ударил головой о стену, как мы!:)

Cheers,

Chris

+0

Я думаю, что я попробовал аналогичное решение, но с проблемой, что поворотное преобразование вращает фактическое содержимое изображения. Но если слой является симметричным изображением, вы его не заметите. –

+0

Можете ли вы объяснить, что такое 'v.theSquare'? – aBikis

1

Вот пример Xamarin IOS я использую лоскут углу квадратной кнопки, как собака уха (легко переносимых в Obj-C):

enter image description here

Метод 1: использование вращение анимации с 1 для обоих x и y осей (примеры в Xamarin.iOS, бу т легко переносимым на Obj-C):

// add to the UIView subclass you wish to rotate, where necessary 
AnimateNotify(0.10, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState,() => 
{ 
    // note the final 3 params indicate "rotate around x&y axes, but not z" 
    var transf = CATransform3D.MakeRotation(-1 * (nfloat)Math.PI/4, 1, 1, 0); 
    transf.m34 = 1.0f/-500; 
    Layer.Transform = transf; 
}, null); 

Метод 2: просто добавить x-axis поворот и y-axis поворота к CAAnimationGroup так они бегут в то же самое время:

// add to the UIView subclass you wish to rotate, where necessary 
AnimateNotify(1.0, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState,() => 
{ 
    nfloat angleTo = -1 * (nfloat)Math.PI/4; 
    nfloat angleFrom = 0.0f ; 
    string animKey = "rotate"; 

    // y-axis rotation 
    var anim = new CABasicAnimation(); 
    anim.KeyPath = "transform.rotation.y"; 
    anim.AutoReverses = false; 
    anim.Duration = 0.1f; 
    anim.From = new NSNumber(angleFrom); 
    anim.To = new NSNumber(angleTo); 

    // x-axis rotation 
    var animX = new CABasicAnimation(); 
    animX.KeyPath = "transform.rotation.x"; 
    animX.AutoReverses = false; 
    animX.Duration = 0.1f; 
    animX.From = new NSNumber(angleFrom); 
    animX.To = new NSNumber(angleTo); 

    // add both rotations to a group, to run simultaneously 
    var animGroup = new CAAnimationGroup(); 
    animGroup.Duration = 0.1f; 
    animGroup.AutoReverses = false; 
    animGroup.Animations = new CAAnimation[] {anim, animX}; 
    Layer.AddAnimation(animGroup, animKey); 

    // add perspective 
    var transf = CATransform3D.Identity; 
    transf.m34 = 1.0f/500; 
    Layer.Transform = transf; 

}, null); 
+0

Не знаете, почему это заставляет проголосовать. Это вращение CALayer вокруг диагональной линии. «Покажите свою работу», downvoter. – bunkerdive

+0

Это действительно приятно! точно, что я ищу. Я не вижу, где вы назначили угловую область. В вашем примере большой угол, который движется как ухо собаки, я хочу небольшое ухо – nikhilgohil11

+0

@ nikhilgohil11, большая область «+» - это отдельная кнопка (UIView). Угловая кнопка на самом деле представляет собой квадрат (наполовину закрытый большой кнопкой), который поворачивается. В итоге получим 2 подклассы Button: BigButton и CornerButton. Поместите экземпляр CornerButton под BigButton (наполовину закрытым), и когда CornerButton получает нажатие, вызовите код вращения. – bunkerdive

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