2012-10-16 42 views
30

我有一個自定義的流佈局,正在調整單元格的屬性,當它們被插入並從以下兩個函數從CollectionView中刪除,但我無法弄清楚如何調整默認動畫持續時間。如何設置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

根據蘋果的文檔「當動畫布局的變化,動畫定時和參數由集合視圖控制。」這是參考setCollectionView:animated:方法,但我懷疑修改集合視圖的邊界也是如此。對不起,我無法提供更多幫助,我陷入了同樣的問題。我懷疑答案在UICollectionView對象本身的某處。 – Ash

回答

22

爲了解決問題,而這是在answer by gavrix 你可以用新的屬性CABasicAnimation *transformAnimation繼承UICollectionViewLayoutAttributes,比用合適的時間創建自定義轉換,並在initialLayoutAttributesForAppearingItemAtIndexPath分配到屬性,然後根據需要UICollectionViewCell應用屬性提出破解:

@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 

在佈局類

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

然後UICollectionViewCell申請屬性

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

您還需要重寫'layoutAttributesClass'以在佈局類中返回'[AnimationCollectionViewLayoutAttributes類]'。 –

+1

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

+1

在你的CustomFlowLayout.m類中添加上面的方法! –

3

UICollectionView啓動所有動畫內部使用一些硬編碼值。但是,您始終可以覆蓋該值直到提交動畫。 在一般情況下,過程是這樣的:

  • 開始動畫
  • 獲取所有佈局attribues
  • 將屬性應用於意見(UICollectionViewCell的)
  • 提交動畫

將屬性被下進行每個UICollectionViewCell都可以用適當的方法覆蓋animationDuration。問題是,UICollectionViewCell有公共方法applyLayoutAttributes:但它的默認實現是空的!基本上,UICollectionViewCell有其他私有方法,稱爲_setLayoutAttributes:,這個私有方法由UICollectionView調用,這個私有方法最後調用applyLayoutAttributes:。在調用applyLayoutAttributes:之前,應用當前的animationDuration應用默認佈局屬性,如框架,位置,變換。 這就是說,你在私有方法_setLayoutAttributes:

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

覆蓋animationDuration這是顯而易見的,不是的AppleStore安全。您可以使用這些運行時攻擊之一來安全地重寫此私有方法。

4

在沒有成功布局過程的每一個可能的相位試圖[CATransaction setAnimationDuration:][UIView setAnimationDuration:]後,我想出了一個有點哈克的方式來改變由UICollectionView產生細胞動畫,不依賴於私有API的持續時間。

您可以使用CALayerspeed屬性來更改給定圖層上執行的動畫的相對媒體時間。爲了與UICollectionView一起使用,您可以在單元的圖層上將layer.speed更改爲小於1的值。很明顯,單元格的層總是具有非統一的動畫速度並不是很好,因此一種選擇是在準備單元格動畫時發送NSNotification,單元格動畫會改變圖層的速度,然後將其更改回來在動畫完成後的適當時間。

我不建議使用這種方法作爲長期的解決方案,因爲它非常迂迴,但它確實有效。希望Apple將在未來爲UICollectionView動畫公開更多選項。

16

變化的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

這很好,很高興地說。這比其他解決方法更容易。我會補充說一個更高的數字意味着更快的動畫。 – sudo

0

您可以更改UICollectionView layout.speed財產,應改變佈局的動畫時間...

3

您可以設置圖層的速度屬性(如[Rotoava's Answer])(https://stackoverflow.com/a/23146861/5271393)來改變控制動畫的速度。問題是你使用的是任意值,因爲你不知道插入動畫的實際持續時間。

使用this post您可以計算出默認的動畫持續時間。

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

如果你想使動畫400ms的長,你的佈局你會:

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

在我來說,我有可能被拖出屏幕細胞,我想改變的時間基於平移手勢的速度的刪除動畫。

在手勢識別(這應該是您的收藏視圖的一部分):

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

然後在您的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

大廈@羅塔瓦的回答,您可以臨時設置動畫速度通過使用集合視圖的批量更新:

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

我想知道布爾'已完成'在這裏是否重要。在某些調用中(我現在不記得確切的是哪個),「完成」塊被多次調用。爲了完全確定動畫已經完成,我會'如果(完成){/ * ... * /}'。爲什麼在這裏沒有必要?或者是,你只是跳過它? –

0

更新到@Ashle因爲forBaselineLayout yMills已被棄用

這工作

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 
})