2010-03-07 12 views
13

Sto provando a implementare un'animazione a flip da utilizzare nel gioco da tavolo come l'applicazione per iPhone. L'animazione dovrebbe assomigliare ad un pezzo di gioco che ruota e cambia al colore della sua schiena (un po 'come un Reversi piece). Sono riuscito a creare un'animazione che capovolge il pezzo attorno al suo asse ortogonale, ma quando provo a capovolgerlo attorno ad un asse diagonale cambiando la rotazione attorno all'asse z anche l'immagine reale viene ruotata (non a caso). Invece vorrei ruotare l'immagine "così com'è" attorno ad un asse diagonale.Come ruoto un CALayer attorno a una linea diagonale?

Ho provato a cambiare layer.sublayerTransform ma senza successo.

Ecco la mia attuale implementazione. Funziona facendo un trucco per risolvere il problema di ottenere un'immagine speculare alla fine dell'animazione. La soluzione è quella di non ruotare realmente il livello di 180 gradi, ma lo ruota di 90 gradi, cambia l'immagine e quindi la ruota indietro.

versione finale: Sulla base Lorenzos suggerimento per creare un'animazione keyed discreto e calcolare la matrice di trasformazione per ogni fotogramma. Questa versione tenta invece di stimare il numero di frame "guidanti" necessari in base alla dimensione del layer e quindi utilizza un'animazione con chiave lineare. Questa versione ruota con un angolo arbitrario in modo da ruotare attorno alla linea diagonale usando un angolo di 45 gradi.

Esempio di utilizzo:

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

Implementazione:

- (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]; 
} 

Codice originale:

+ (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

Che è un asse diagonale? – kennytm

+0

Per asse diagonale intendo che la rotazione dovrebbe essere intorno alla diagonale del livello. Come se avessi afferrato due angoli diagonali dello strato e girandolo. –

+2

Come, ruotare attorno alla retta 'y = x'? – kennytm

risposta

6

si può fingere in questo modo: crea un trasformazione affine che il collasso dello strato lungo è diagonale:

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

cambiare l'immagine e trasformare il CALayer in un'altra animazione. Questo creerà l'illusione dello strato che gira attorno alla sua diagonale.

la matrice per questo dovrebbe essere, se non ricordo male la matematica:

0.5 0.5 0 
0.5 0.5 0 
0 0 1 

Aggiornamento: ok, CA doen't davvero ama usare trasforma degenerati, ma si può approssimare in questo modo:

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

nei miei test sul simulatore c'era ancora un problema perché le rotazioni avvengono più velocemente della traduzione quindi con un quadrato nero pieno l'effetto era un po 'strano. Suppongo che se hai uno sprite centrato con un'area trasparente attorno ad esso l'effetto sarà vicino a quanto atteso. È quindi possibile modificare il valore della matrice t3 per vedere se si ottiene un risultato più accattivante.

dopo ulteriori ricerche, sembra che si debba animare la propria transizione tramite i fotogrammi chiave per ottenere il massimo controllo della transizione stessa.diciamo che dovresti mostrare questa animazione in un secondo, dovresti fare in modo che dieci matrici vengano mostrate ogni decimo di secondo senza interpolazione usando kCAAnimationDiscrete; quelli matrice può essere generato tramite il codice qui sotto:

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

dove animationStepValue per ech del fotogramma chiave è preso da questa progressione:

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1} 

che è: si sta generando dieci differente matrice di trasformazione (in realtà 9), spingendoli come fotogrammi chiave da mostrare a ciascun decimo di secondo e quindi utilizzando il parametro "non interpolare". puoi modificare il numero dell'animazione per bilanciare fluidità e prestazioni *

* scusa per eventuali errori, quest'ultima parte è stata scritta senza un correttore ortografico.

+0

Grazie per la risposta. Supponiamo che l'arte ASCII mostri una trasformazione al taglio? se provo ad animare dall'identità, poi alla tua matrice e loro tornano all'identità non ottengo alcuna immagine :( Ho cercato come creare una matrice di taglio e assomiglia a questa: {{1, cimosa, 0} , {shearx, 1, 0}, {0, 0, 1}} Se lo uso nell'animazione, sembra funzionare un po ', ma ho bisogno di tosare molto e quindi l'immagine viene ingrandita un po' troppo. –

+0

nessun taglio farà un parallelogramma, qui stiamo cercando di ottenere un rombo degenerato, con l'angolo A & D che si muove verso il centro e non gli angoli A & B che si spostano verso sinistra Dovrò provare questo sul dispositivo domani per ottenere i parametri attuali della matrice –

+0

Ok. Ho letto come funzionano effettivamente le trasformazioni affini e la trasformazione che descrivi con il diagramma dovrebbe, come dici tu, essere affine, preservare i punti su una linea e su un ratto ios (finché A e D sono "opposti", immagino?). Ma non ho davvero capito come pensare ai numeri nella matrice. –

4

Ho risolto. Probabilmente hai già una soluzione, ma ecco quello che ho trovato. È davvero semplice ...

È possibile utilizzare un CABasicAnimation per eseguire la rotazione diagonale, ma deve essere la concatenazione di due matrici, vale a dire la matrice esistente del livello, più un CATransform3DRotate. Il "trucco" è, nel 3DRotate è necessario specificare le coordinate per ruotare intorno.

Il codice simile a questa:


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

Questo farà una rotazione che appare come se l'angolo superiore sinistro della piazza ruota attorno all'asse Y = X, e parte per il basso a angolo destro.

Il codice per animare aspetto:


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"]; 

Questo è tutto! Quel codice ruoterà il quadrato (theLayer) in invisibilità mentre viaggia a 90 gradi e si presenta ortogonalmente allo schermo. Puoi quindi cambiare il colore e fare esattamente la stessa animazione per riportarlo indietro. La stessa animazione funziona perché concateniamo le matrici, quindi ogni volta che si desidera ruotare, è sufficiente farlo due volte o cambiare M_PI/2 in M_PI.

Infine, va notato, e questo mi ha fatto impazzire, che al termine, il livello tornerà al suo stato originale a meno che non lo si imposti esplicitamente sullo stato di animazione finale. Ciò significa che, poco prima della linea di [theLayer addAnimation:ani1 [email protected]"arbitraryKey"]; si vuole aggiungere


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

per impostare il suo valore per dopo l'animazione completa. Ciò impedirà lo snapback allo stato originale.

Spero che questo aiuti. Se no, allora forse qualcun altro che stava sbattendo la testa contro il muro come noi!:)

Cheers,

Chris

+0

Penso di aver provato una soluzione simile ma con il problema che la trasformazione di rotazione ruota il contenuto dell'immagine reale. Ma se il livello è un'immagine simmetrica non lo noterai. –

+0

puoi spiegare cosa c'è "v.thequadrata"? – aBikis

1

Ecco un esempio Xamarin iOS io uso per sbattere all'angolo di un pulsante quadrato, come un orecchio del cane (facilmente portato su Obj-C):

enter image description here

Metodo 1: uso un'animazione rotazione con 1 per entrambi gli assi x e y (esempi in Xamarin.iOS, bu t facilmente portabile a 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); 

Metodo 2: sufficiente aggiungere una rotazione x-axis e y-axis rotazione ad una CAAnimationGroup modo da funzionare allo stesso tempo:

// 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

Non so perché questo fa un down-vote. Questa è una rotazione di CALayer su una linea diagonale. "Mostra il tuo lavoro", downvoter. – bunkerdive

+0

Davvero bello! esattamente quello che sto cercando. Non vedo dove è stata assegnata l'area d'angolo, Nell'angolo di esempio è grande che si muove come l'orecchio di un cane Voglio un piccolo orecchio – nikhilgohil11

+0

@ nikhilgohil11, la grande area "+" è un pulsante separato (UIView). Il pulsante d'angolo è in realtà un quadrato (metà coperto da un grande pulsante) che viene ruotato. In breve, derivano le sottoclassi di 2 Button: BigButton e CornerButton. Metti un'istanza CornerButton sotto un BigButton (parzialmente coperto), e quando CornerButton viene premuto, chiama il codice di rotazione. – bunkerdive

Problemi correlati