2012-10-16 14 views
30

Mam niestandardowy układ przepływu, który dostosowuje atrybuty dla komórek, gdy są one wstawiane i usuwane z CollectionView z następującymi dwiema funkcjami, ale nie jestem w stanie dowiedzieć się, jak dostosować domyślny czas trwania animacji.Jak ustawić czas trwania animacji UICollectionView?

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

Według dokumentacji Apple: „Kiedy animowanie zmiany układu, czas animacji i parametry są kontrolowane przez widoku kolekcji”. Jest to w odniesieniu do metody setCollectionView: animated:, ale podejrzewam, że to samo dotyczy modyfikacji granic widoku kolekcji. Przepraszam, nie mogę pomóc, utknąłem na tym samym problemie. Podejrzewam, że odpowiedź leży gdzieś w samym obiekcie UICollectionView. – Ash

Odpowiedz

22

Aby rozwiązać problem bez włamania, które zostały zaproponowane w answer by gavrix można podklasy UICollectionViewLayoutAttributes z nowej nieruchomości CABasicAnimation *transformAnimation, niż utworzyć niestandardową transformację z odpowiednim czasie i przypisać go do atrybutów w initialLayoutAttributesForAppearingItemAtIndexPath, następnie w UICollectionViewCell zastosować atrybuty jako potrzebne:

@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 

w klasie Układ

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

następnie zastosować w UICollectionViewCell atrybuty

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

Musisz także zastąpić '+ layoutAttributesClass', aby zwrócić' [AnimationCollectionViewLayoutAttributes class] 'w klasie layoutu. –

+1

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

+1

dodaj powyższą metodę w swojej klasie CustomFlowLayout.m! –

3

UICollectionView inicjuje wszystkie animacje wewnętrznie przy użyciu jakiegoś sztywno wartości. Jednak zawsze można przesłonić tę wartość, dopóki animacje nie zostaną zatwierdzone. Na ogół proces wygląda następująco:

  • rozpocząć animacje
  • pobrać wszystkie attribues układu
  • zastosować atrybuty do poglądów (UICollectionViewCell'S)
  • popełnienia animacje

stosowania atrybutów jest robione pod każdy UICollectionViewCell i można zastąpić animationDuration w odpowiedniej metodzie. Problem polega na tym, że UICollectionViewCell ma publiczną metodę applyLayoutAttributes:, ale jej domyślna implementacja jest pusta !. Zasadniczo, UICollectionViewCell ma inną prywatną metodę o nazwie _setLayoutAttributes: i ta prywatna metoda jest wywoływana przez UICollectionView i ta prywatna metoda wywołuje na końcu applyLayoutAttributes:. Domyślne atrybuty układu, takie jak ramka, pozycja, transformacja, są stosowane z bieżącym animationDuration przed wywołaniem applyLayoutAttributes:. Powiedział, że trzeba zastąpić animationDuration w metodzie prywatnej _setLayoutAttributes:

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

To oczywiście nie AppleStore bezpieczny. Możesz użyć jednego z tych hacków środowiska wykonawczego, aby w bezpieczny sposób zastąpić tę prywatną metodę.

4

Po wypróbowaniu [CATransaction setAnimationDuration:] i [UIView setAnimationDuration:] w każdym możliwym etapie procesu układu bez powodzenia, zorientowali się nieco hacky sposób na zmianę czasu trwania animacji stworzonych przez UICollectionView komórkowych, które nie opierają się na prywatnej API.

Możesz użyć właściwości CALayer 'speed, aby zmienić względny czas odtwarzania animacji na danej warstwie. Aby to zadziałało z UICollectionView, możesz zmienić layer.speed na mniej niż 1 w warstwie komórki. Oczywiście nie jest wspaniale, że warstwa komórki ZAWSZE ma prędkość animacji niejednoczesną, więc jedną z opcji jest wysłanie NSNotification podczas przygotowywania animacji komórki, do której subskrybują twoje komórki, która zmieni szybkość warstwy, a następnie zmieni ją z powrotem w odpowiednim czasie po zakończeniu animacji.

Nie polecam stosowania tego podejścia jako rozwiązania długotrwałego, ponieważ jest dość okrężne, ale działa. Mam nadzieję, że Apple udostępni więcej opcji dla animacji UICollectionView w przyszłości.

16

zmiana prędkości CALayer za

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

To działa doskonale, z przyjemnością powiedzieć. To znacznie łatwiejsze niż inne rozwiązania. Dodam, że wyższa liczba oznacza szybsze animacje. – sudo

0

można zmienić UICollectionView layout.speed właściwość, że powinien zmienić czas trwania animacji układu ...

3

Można ustawić właściwość prędkości w warstwie (jak w [Odpowiedź Rotoavy]) (https://stackoverflow.com/a/23146861/5271393), aby zmienić kontrolę szybkości animacji. Problem polega na tym, że używasz arbitralnych wartości, ponieważ nie znasz faktycznego czasu trwania animacji wstawiania.

Za pomocą this post można ustalić, jaki jest domyślny czas trwania animacji.

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

Jeśli chcesz, aby długo 400ms animacja, w układzie byś:

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

W moim przypadku miałem komórki, które mogą być przeciągane poza ekranem i chciałem zmienić czas trwania animacja usuwania w oparciu o szybkość gestu panoramy.

W rozpoznawania gestów (które powinny być częścią widoku kolekcji):

- (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); 
    ... 
    } 
    ... 
} 

Następnie w 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

Opierając się na @ Rotava odpowiedź, można chwilowo ustawić animację szybkość przy użyciu aktualizacji wsadowej widoku kolekcji:

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

Zastanawiam się, czy boolean 'finished' jest tutaj ważny. Przy niektórych połączeniach (nie pamiętam dokładnie, które są teraz), blok "zakończenia" jest wywoływany więcej niż jeden raz. Aby mieć całkowitą pewność, że animacje się skończyły, zrobiłbym "if (finished) {/ * ... * /}". Dlaczego nie jest to konieczne? Czy to prawda i po prostu go pominąłeś? –

0

Aktualizacja @Ashle yMills od forBaselineLayout jest przestarzała

To działa

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 
}) 
Powiązane problemy