【问题标题】:How do you set the duration for UICollectionView Animations?你如何设置 UICollectionView 动画的持续时间?
【发布时间】:2012-10-07 00:43:06
【问题描述】:

我有一个自定义流布局,当单元格从 CollectionView 插入和删除时,它使用以下两个函数调整它们的属性,但我无法弄清楚如何调整默认动画持续时间。

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

【问题讨论】:

  • 根据苹果的文档,“当动画布局发生变化时,动画时间和参数由集合视图控制。”这是参考 setCollectionView:animated: 方法,但我怀疑修改集合视图的边界也是如此。抱歉,我无法提供更多帮助,我遇到了同样的问题。我怀疑答案就在 UICollectionView 对象本身的某个地方。

标签: ios ios6 grid collectionview


【解决方案1】:

解决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;
}

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

然后在 UICollectionViewCell 中应用属性

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

【讨论】:

  • 您还需要在布局类中覆盖+layoutAttributesClass 以返回[AnimationCollectionViewLayoutAttributes class]
  • + (Class)layoutAttributesClass { return [AnimationCollectionViewLayoutAttributes class]; }
  • 在您的 CustomFlowLayout.m 类中添加上述方法!
【解决方案2】:

改变CALayer的速度

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

【讨论】:

  • 这很完美,很高兴说。它比其他解决方法更容易。我要补充一点,数字越大意味着动画越快。
【解决方案3】:

基于@rotava 的回答,您可以通过使用集合视图的批量更新来临时设置动画速度:

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

【讨论】:

  • 我想知道布尔值finished 在这里是否重要。在某些调用中(我现在不记得具体是哪些调用了),completion 块被多次调用。为了完全确定动画已经完成,我会做if ( finished ) { /* ... */ }。为什么这里没有必要?还是你只是跳过了它?
  • 如果performBatchUpdates 有可能在之前的动画正在进行时被调用,将层的速度设置回 1 将导致之前的动画“向前跳跃”(随着时间缩放的变化),甚至到 final职位。如果您不需要任何其他动画(performBatchUpdates 的动画除外),您可以设置图层的速度并保持不变。
【解决方案4】:

在布局过程的每个可能阶段尝试[CATransaction setAnimationDuration:][UIView setAnimationDuration:] 均未成功后,我想出了一种不依赖私有API 的更改UICollectionView 创建的单元格动画的持续时间的有点骇人听闻的方法.

您可以使用CALayerspeed 属性来更改在给定层上执行的动画的相对媒体时间。要使其与UICollectionView 一起使用,您可以在单元格层上将layer.speed 更改为小于1 的值。显然,让单元格的图层始终具有非统一动画速度并不是很好,因此一种选择是在准备单元格动画时发送NSNotification,您的单元格订阅该动画,这将改变层速度,然后改变在动画完成后的适当时间返回。

我不建议将这种方法用作长期解决方案,因为它非常迂回,但它确实有效。希望 Apple 将来会为 UICollectionView 动画提供更多选项。

【讨论】:

    【解决方案5】:

    UICollectionView 使用一些硬编码值在内部启动所有动画。但是,在提交动画之前,您始终可以覆盖该值。 一般来说,流程如下所示:

    • 开始动画
    • 获取所有布局属性
    • 将属性应用于视图(UICollectionViewCell's)
    • 提交动画

    应用属性是在每个 UICollectionViewCell 下完成的,您可以在适当的方法中覆盖 animationDuration。问题是 UICollectionViewCell 有公共方法applyLayoutAttributes: 但它的默认实现是空的!基本上,UICollectionViewCell 有另一个名为_setLayoutAttributes: 的私​​有方法,这个私有方法由 UICollectionView 调用,这个私有方法最后调用applyLayoutAttributes:。在调用applyLayoutAttributes: 之前,默认布局属性,如框架、位置、变换与当前animationDuration 一起应用。 也就是说,您必须在私有方法 _setLayoutAttributes: 中覆盖 animationDuration

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

    这显然不是苹果商店安全的。您可以使用其中一种运行时技巧来安全地覆盖此私有方法。

    【讨论】:

      【解决方案6】:

      您可以设置图层的速度属性(如Rotoava's Answer)来改变控制动画的速度。问题是您使用的是任意值,因为您不知道插入动画的实际持续时间。

      使用this post 可以计算出默认动画持续时间是多少。

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

      如果你想让动画长 400 毫秒,在你的布局中你会:

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

      然后在你的自定义布局中:

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

      【讨论】:

        【解决方案7】:

        @AshleyMills 的更新,因为 forBaselineLayout 已弃用

        这行得通

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

        【讨论】:

          【解决方案8】:

          您可以更改 UICollectionView layout.speed 属性,这应该会更改布局的动画持续时间...

          【讨论】:

            【解决方案9】:

            没有子类化:

            [UIView animateWithDuration:2.0 animations:^{
              [self.collection reloadSections:indexSet];
            }];
            

            【讨论】:

            • 我很惊讶这是最底层的答案。为我工作。我在 UIView 动画闭包中完成了我的 performBatchUpdates 而不是 reloadSections
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-08-28
            • 2019-04-09
            • 1970-01-01
            • 1970-01-01
            • 2011-11-29
            • 1970-01-01
            相关资源
            最近更新 更多