2012-10-16 15 views
30

Ho un layout di flusso personalizzato che regola gli attributi per le celle quando vengono inseriti e cancellati da CollectionView con le seguenti due funzioni, ma non sono in grado di capire come regolare la durata dell'animazione predefinita.Come si imposta la durata per UICollectionView Animations?

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { 
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; 

// Assign the new layout attributes 
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5); 
attributes.alpha = 0; 

return attributes; 

}

-(UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ 

UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; 

// Assign the new layout attributes 
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5); 
attributes.alpha = 0; 

return attributes; 

}

+0

Secondo la documentazione di Apple, "Durante l'animazione modifiche del layout, la tempistica dell'animazione e parametri sono controllati da vista collezione." Questo è in riferimento al metodo setCollectionView: animated: ma sospetto che lo stesso sia vero per la modifica dei limiti della vista di raccolta. Scusa non posso essere più di aiuto, sono bloccato sullo stesso problema. Sospetto che la risposta sia da qualche parte all'interno dell'oggetto UICollectionView stesso. – Ash

risposta

22

per risolvere il problema senza trucco che è stato proposto nel answer by gavrix si potrebbe sottoclasse UICollectionViewLayoutAttributes con nuova proprietà CABasicAnimation *transformAnimation, che creare trasformazione personalizzato con una durata adeguata e assegnarlo a attributi in initialLayoutAttributesForAppearingItemAtIndexPath, poi nel UICollectionViewCell applicare gli attributi in base alle esigenze:

@interface AnimationCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes 
@property (nonatomic, strong) CABasicAnimation *transformAnimation; 
@end 

@implementation AnimationCollectionViewLayoutAttributes 
- (id)copyWithZone:(NSZone *)zone 
{ 
    AnimationCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone]; 
    attributes.transformAnimation = _transformAnimation; 
    return attributes; 
} 

- (BOOL)isEqual:(id)other { 
    if (other == self) { 
     return YES; 
    } 
    if (!other || ![[other class] isEqual:[self class]]) { 
     return NO; 
    } 
    if ([((AnimationCollectionViewLayoutAttributes *) other) transformAnimation] != [self transformAnimation]) { 
     return NO; 
    } 

    return YES; 
} 
@end 

in classe layout

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { 
    AnimationCollectionViewLayoutAttributes* attributes = (AnimationCollectionViewLayoutAttributes*)[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; 

    CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; 
    transformAnimation.duration = 1.0f; 
    CGFloat height = [self collectionViewContentSize].height; 

    transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 2*height, height)]; 
    transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, attributes.bounds.origin.y, 0)]; 
    transformAnimation.removedOnCompletion = NO; 
    transformAnimation.fillMode = kCAFillModeForwards; 
    attributes.transformAnimation = transformAnimation; 
    return attributes; 
} 

poi applicare UICollectionViewCell gli attributi

- (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes 
{ 
    [[self layer] addAnimation:layoutAttributes.transformAnimation forKey:@"transform"]; 
} 
+5

È inoltre necessario sovrascrivere '+ layoutAttributesClass' per restituire' [AnimationCollectionViewLayoutAttributes class] 'nella classe di layout. –

+1

'+ (Class) layoutAttributesClass { return [AnimationCollectionViewLayoutAttributes class]; } ' –

+1

aggiungi il metodo sopra nella tua classe CustomFlowLayout.m! –

3

UICollectionView avvia tutte le animazioni internamente utilizzando alcuni un valore predefinito. Tuttavia, è sempre possibile sovrascrivere quel valore fino a quando non vengono eseguite le animazioni. In generale, il processo si presenta come segue:

  • cominciano animazioni
  • recuperare tutti attribues di layout
  • applicare gli attributi di vista (di UICollectionViewCell)
  • commettere animazioni

per applicare gli attributi è fatto sotto ogni UICollectionViewCell e puoi sovrascrivere animationDuration nel metodo appropriato. Il problema è che UICollectionViewCell ha il metodo pubblico applyLayoutAttributes:, ma l'implementazione predefinita è vuota !. Fondamentalmente, UICollectionViewCell ha un altro metodo privato chiamato _setLayoutAttributes: e questo metodo privato viene chiamato da UICollectionView e questo metodo privato chiama applyLayoutAttributes: alla fine. Gli attributi di layout predefiniti, come frame, position, transform vengono applicati con lo animationDuration corrente prima che venga chiamato lo applyLayoutAttributes:. Detto questo, è necessario eseguire l'override animationDuration nel metodo privato _setLayoutAttributes:

- (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes 
{ 
    [UIView setAnimationDuration:3.0]; 
    [super _setLayoutAttributes:layoutAttributes]; 
} 

Questo è ovviamente, non AppleStore-safe. È possibile utilizzare uno di questi hack di runtime per sovrascrivere questo metodo privato in modo sicuro.

4

Dopo aver provato [CATransaction setAnimationDuration:] e [UIView setAnimationDuration:] in ogni possibile fase del processo di layout senza successo, ho trovato un modo un po 'hacky per modificare la durata delle animazioni create da cellule UICollectionView che non si basa sulle API private.

È possibile utilizzare la proprietà speed diper modificare il tempo di media relativo delle animazioni eseguite su un determinato livello. Affinché questo funzioni con UICollectionView, è possibile modificare layer.speed in un valore inferiore a 1 nel livello della cella. Ovviamente non è fantastico avere il layer della cella SEMPRE con una velocità di animazione non unitaria, quindi un'opzione è quella di inviare un NSNotification quando si preparano per le animazioni di celle, a cui le tue cellule si iscrivono, che cambieranno la velocità del livello, e poi lo cambieranno indietro al momento opportuno dopo che le animazioni sono terminate.

Non è consigliabile utilizzare questo approccio come soluzione a lungo termine in quanto è piuttosto rotonda, ma funziona. Speriamo che Apple esporterà più opzioni per le animazioni di UICollectionView in futuro.

16

cambio di velocità del CALayer

@implementation Cell 
- (id)initWithFrame:(CGRect)frame 
{ 
self = [super initWithFrame:frame]; 
if (self) { 
    self.layer.speed =0.2;//default speed is 1 
} 
return self; 
} 
+4

Funziona perfettamente, felice di dirlo. È molto più semplice degli altri metodi. Aggiungerò che un numero più alto significa animazioni più veloci. – sudo

0

È possibile modificare UICollectionView layout.speed proprietà, che dovrebbe modificare la durata dell'animazione del layout ...

3

È possibile impostare la proprietà di velocità del livello (come in [Rotoava's Answer]) (https://stackoverflow.com/a/23146861/5271393) per cambiare il controllo della velocità dell'animazione. Il problema è che stai usando valori arbitrari perché non conosci la durata effettiva dell'animazione di inserimento.

Utilizzando this post è possibile capire qual è la durata predefinita dell'animazione.

newAnimationDuration = (1/layer.speed)*originalAnimationDuration 
layer.speed = originalAnimationDuration/newAnimationDuration 

Se si voleva fare lungo i 400ms di animazione, nel layout si farebbe:

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; 
    //set attributes here 
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 
    CGFloat originalAnimationDuration = [CATransaction animationDuration]; 
    CGFloat newAnimationDuration = 0.4f; 
    cell.layer.speed = originalAnimationDuration/newAnimationDuration; 
    return attributes; 
} 

Nel mio caso ho avuto le cellule che potrebbero essere trascinati fuori dallo schermo e ho voluto cambiare la durata di l'animazione della cancellazione in base alla velocità del gesto pan.

Nel riconoscitore gesto (che dovrebbe essere parte della vostra vista collezione):

- (void)handlePanGesture:(UIPanGestureRecognizer *)sender 
{ 
    CGPoint dragVelocityVector = [sender velocityInView:self.collectionView]; 
    CGFloat dragVelocity = sqrt(dragVelocityVector.x*dragVelocityVector.x + dragVelocityVector.y*dragVelocityVector.y); 
    switch (sender.state) { 
    ... 
    case UIGestureRecognizerStateChanged:{ 
     CustomLayoutClass *layout = (CustomLayoutClass *)self.collectionViewLayout; 
     layout.dragSpeed = fabs(dragVelocity); 
    ... 
    } 
    ... 
} 

Poi, nel tuo customLayout:

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; 
    CGFloat animationDistance = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); 
    CGFloat originalAnimationDuration = [CATransaction animationDuration]; 
    CGFloat newAnimationDuration = animationDistance/self.dragSpeed; 
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 
    cell.layer.speed = originalAnimationDuration/newAnimationDuration; 
    return attributes; 
} 
8

Sulla @ di Rotava risposta, è possibile impostare temporaneamente l'animazione velocità utilizzando un aggiornamento batch della vista raccolta:

[self.collectionView performBatchUpdates:^{ 
    [self.collectionView.viewForBaselineLayout.layer setSpeed:0.2]; 
    [self.collectionView insertItemsAtIndexPaths: insertedIndexPaths]; 
} completion:^(BOOL finished) { 
    [self.collectionView.viewForBaselineLayout.layer setSpeed:1]; 
}]; 
+1

Mi chiedo se il "valore finale" booleano sia importante qui. Ad alcune chiamate (non ricordo esattamente quali sono adesso), il blocco 'completamento' viene chiamato più di una volta. Per essere completamente sicuro che le animazioni siano terminate, farei 'if (finished) {/ * ... * /}'. Perché non è necessario qui? O è e l'hai appena saltato? –

0

Un aggiornamento a @Ashle yMills dal forBaselineLayout è deprecato

Questo funziona

self.collectionView.performBatchUpdates({() -> Void in 
     let indexSet = IndexSet(0...(numberOfSections - 1)) 
     self.collectionView.insertSections(indexSet) 
     self.collectionView.forFirstBaselineLayout.layer.speed = 0.5 
} , completion: { (finished) -> Void in 
     self.collectionView.forFirstBaselineLayout.layer.speed = 1.0 
}) 
Problemi correlati