2010-05-26 16 views
22

Ho una sottoclasse UIView che utilizza una maschera CAShapeLayer sul suo CALayer. La maschera utilizza una forma distinta, con tre angoli arrotondati e un rettangolo ritagliato nell'angolo rimanente.Animazione della modifica della dimensione di una maschera di CALayer

Quando a ridimensionare la mia UIView utilizzando un blocco di animazione standard, il UIView stessa e il suo ridimensionamento CALayer bene. La maschera, tuttavia, viene applicata immediatamente, il che porta ad alcuni problemi di disegno.

Ho provato ad animare il ridimensionamento della maschera utilizzando un CABasicAnimation ma non ho avuto fortuna ad ottenere il ridimensionamento animato.

Posso ottenere in qualche modo un effetto di ridimensionamento animato sulla maschera? Devo liberarmi della maschera, o dovrò cambiare qualcosa sul modo in cui attualmente disegno la maschera (usando - (void)drawInContext:(CGContextRef)ctx).

Cheers, Alex

risposta

37

Ho trovato la soluzione a questo problema. Altre risposte sono parzialmente corrette e sono utili.

I seguenti punti sono importanti per comprendere la soluzione:

  • struttura La maschera non è animabili stessa.
  • Poiché la maschera è un CALayer, può essere animata autonomamente.
  • La cornice non è animabile, usa limiti e posizione. Questo potrebbe non essere applicabile a te (se non stavi cercando di animare la cornice), ma era un problema per me. (Vedi Apple QA 1620)
  • La maschera di un livello di vista non è legata a UIView, quindi non riceverà la transazione dell'anima di base applicata al livello della vista.
  • Stiamo modificando direttamente il CALayer, quindi non possiamo aspettarci che UIView abbia alcuna idea di cosa stiamo cercando di fare, quindi l'animazione UIView non creerà la transazione dell'anima core per includere le modifiche alle nostre proprietà.

Per risolvere, dovremo attingere direttamente a Core Animation e non possiamo fare affidamento sul blocco di animazione di UIView per eseguire il lavoro per noi.

Basta creare uno CATransaction con la stessa durata che si sta utilizzando con [UIView animateWithDuration:...]. Ciò creerà un'animazione separata, ma se la durata e la funzione di andamento sono uguali, dovrebbe animare esattamente con le altre animazioni nel blocco di animazione.

NSTimeInterval duration = 0.5;// match this to the value of the UIView animateWithDuration: call 

[CATransaction begin]; 
[CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration]; 

self.myView.layer.mask.position = CGPointMake(newX, 0); 
self.myView.layer.mask.bounds = CGRectMake(0, 0, newWidth, newHeight); 

[CATransaction commit]; 
+0

avete intenzione di postare un esempio? –

+0

@rockyraccoon scusate, questo è troppo vecchio Non sono in grado di produrre un esempio completo. Se hai una domanda specifica potrei essere in grado di aiutarti. – Kekoa

5

La proprietà maschera di CALayer non è animatable che spiega la vostra mancanza di fortuna in quella direzione.

Il disegno della maschera dipende dal frame/bounds della maschera? (Puoi fornire del codice?) La maschera ha bisogno di set di proprietà DisplayOnBoundsChange?

Cheers, Corin

+0

Ho lo stesso problema qui e sì , la maschera si adatta ai limiti della vista/dello strato. Sta aggiungendo angoli arrotondati. La proprietà è impostata. – Krumelur

2

Il parametro maschera non anima, ma è possibile animare il livello che è impostato come maschera ...

Se animate proprietà Percorso del CAShapeLayer, che dovrebbe animare la maschera. Posso verificare che questo funzioni dai miei progetti. Non sono sicuro di usare comunque una maschera non vettoriale. Hai provato ad animare la proprietà dei contenuti della maschera?

Grazie, Jon

+0

Potresti darmi un codice di esempio per questo? Ho CAShapeLayer, quindi sto chiamando setPath su di esso, quindi sto impostando uiview setMask: shapeLayer ... ed è visibile come calayer ora. Come accedere a questo percorso e modificarlo? Grazie! – Piotr

+0

Posso effettivamente animare il livello che ha la maschera ma solo dopo aver rimosso il suo delegato che lo accoppia alla UIView. Quindi posso ridimensionare il livello senza problemi. Tuttavia la maschera (gli angoli arrotondati) non verrà aggiornata durante il ridimensionamento. E nemmeno il contenuto della vista viene ridimensionato. – Krumelur

0

non ho potuto trovare alcuna soluzione programmatica quindi ho solo disegnare un'immagine png con i valori di forma e alfa corretti e che invece utilizzati. In questo modo non ho bisogno di usare una maschera ...

0

È possibile animare il cambio maschera.

Preferisco utilizzare CAShapeLayer come livello maschera. È molto utile animare una modifica della maschera con l'aiuto del percorso delle proprietà.

Prima di animare qualsiasi modifica, scaricare il contenuto della sorgente in un'istanza CGImageRef e creare un nuovo livello per l'animazione. Nascondi il livello originale durante l'animazione e mostralo quando termina l'animazione.

Quanto segue è un codice di esempio per la creazione dell'animazione della chiave sul percorso della proprietà. Se si desidera creare l'animazione del proprio percorso, assicurarsi che ci sia sempre lo stesso numero di punti nei percorsi.

- (CALayer*)_mosaicMergeLayer:(CGRect)bounds content:(CGImageRef)content isUp:(BOOL)isUp { 

    CALayer* layer = [CALayer layer]; 
    layer.frame = bounds; 
    layer.backgroundColor = [[UIColor clearColor] CGColor]; 
    layer.contents = (id)content; 

    CAShapeLayer* maskLayer = [CAShapeLayer layer]; 
    maskLayer.fillColor = [[UIColor blackColor] CGColor]; 
    maskLayer.frame = bounds; 
    maskLayer.fillRule = kCAFillRuleEvenOdd; 
    maskLayer.path = (isUp ? [self _maskArrowUp:-bounds.size.height*2] : [self _maskArrowDown:bounds.size.height*2]); 
    layer.mask = maskLayer; 

    CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:@"path"]; 
    ani.removedOnCompletion = YES; 
    ani.duration = 0.3f; 
    ani.fillMode = kCAFillModeForwards; 
    ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 

    NSArray* values = (isUp ? 
     [NSArray arrayWithObjects: 
     (id)[self _maskArrowUp:0], 
     (id)[self _maskArrowUp:-ceilf(bounds.size.height*1.2)], 
     nil] 
    :  
     [NSArray arrayWithObjects: 
     (id)[self _maskArrowDown:0], 
     (id)[self _maskArrowDown:bounds.size.height], 
     nil] 
    ); 
    ani.values = values; 
    ani.delegate = self; 
    [maskLayer addAnimation:ani forKey:nil]; 

    return layer; 
} 

- (void)_startMosaicMergeAni:(BOOL)up { 

    CALayer* overlayer = self.aniLayer; 
    CGRect bounds = overlayer.bounds; 
    self.firstHalfAni = NO; 

    CALayer* frontLayer = nil; 
    frontLayer = [self _mosaicMergeLayer:bounds 
           content:self.toViewSnapshot 
           isUp:up]; 
    overlayer.contents = (id)self.fromViewSnapshot; 
    [overlayer addSublayer:frontLayer]; 
} 
8

Utilizzo un CAShapeLayer per mascherare un UIView modificando self.layer.mask a tale livello forma.

Per animare la maschera ogni volta che si cambia la dimensione della vista, ho sovrascritto lo -setBounds: per animare il percorso del livello maschera se i limiti vengono modificati durante un'animazione.

Ecco come ho implementato è:

- (void)setBounds:(CGRect)bounds 
{ 
    [super setBounds:bounds]; 
    CAPropertyAnimation *boundsAnimation = (CABasicAnimation *)[self.layer animationForKey:@"bounds"]; 

    // update the mask 
    self.maskLayer.frame = self.layer.bounds; 

    // if the bounds change happens within an animation, also animate the mask path 
    if (!boundsAnimation) { 
     self.maskLayer.path = [self createMaskPath]; 
    } else { 
     // copying the original animation allows us to keep all animation settings 
     CABasicAnimation *animation = [boundsAnimation copy]; 
     animation.keyPath = @"path"; 

     CGPathRef newPath = [self createMaskPath]; 
     animation.fromValue = (id)self.maskLayer.path; 
     animation.toValue = (__bridge id)newPath; 

     self.maskLayer.path = newPath; 

     [self.maskLayer addAnimation:animation forKey:@"path"]; 
    } 
} 

(Per l'esempio self.maskLayer è impostata a `self.layer.mask)

mio -createMaskPath calcola il CGPathRef che uso per mascherare la vista. Aggiorno anche il percorso della maschera in -layoutSubviews.

3

per animare il limite cambiamento dello strato maschera di un UIView: UIView sottoclasse, e animare la maschera con un CATransaction - simile a Kekodas risposta, ma più generale:

@implementation UIMaskView 

- (void) layoutSubviews { 
    [super layoutSubviews]; 

    CAAnimation* animation = [self.layer animationForKey:@"bounds"]; 
    if (animation) { 
     [CATransaction begin]; 
     [CATransaction setAnimationDuration:animation.duration]; 
    } 

    self.layer.mask.bounds = self.layer.bounds; 
    if (animation) { 
     [CATransaction commit]; 
    } 
} 

@end 
+0

La chiave deve essere 'bounds.size', non' bounds'. –

+0

vale anche la corrispondenza con la funzione di temporizzazione – Sam

Problemi correlati