41

我有一个自定义流布局,它在使用以下两个函数从 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;
}
4

9 回答 9

29

要解决gavrix 在答案中提出的不破解问题, 您可以使用新属性对 UICollectionViewLayoutAttributes 进行子类化CABasicAnimation *transformAnimation,而不是创建具有合适持续时间的自定义转换并将其分配给 in 中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"];
}
于 2014-02-26T15:24:38.483 回答
25

改变CALayer的速度

@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
    self.layer.speed =0.2;//default speed  is 1
}
return self;
}
于 2014-04-18T03:20:51.183 回答
12

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

[self.collectionView performBatchUpdates:^{
    [self.collectionView.viewForBaselineLayout.layer setSpeed:0.2];
    [self.collectionView insertItemsAtIndexPaths: insertedIndexPaths];
} completion:^(BOOL finished) {
    [self.collectionView.viewForBaselineLayout.layer setSpeed:1];
}];
于 2016-02-05T10:26:49.960 回答
5

在尝试[CATransaction setAnimationDuration:]并且[UIView setAnimationDuration:]在布局过程的每个可能阶段都没有成功之后,我想出了一种有点hacky的方法来改变由它创建的单元动画的持续时间UICollectionView,它不依赖于私有API。

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

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

于 2013-08-12T20:59:18.577 回答
4

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

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

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

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

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

于 2013-02-28T21:35:16.090 回答
4

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

使用这篇文章,您可以弄清楚默认动画持续时间是多少。

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

然后在您的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;
}
于 2015-09-14T19:26:41.123 回答
1

自 forBaselineLayout 已弃用以来对@AshleyMills 的更新

这有效

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
})
于 2017-08-09T13:30:51.697 回答
1

没有子类化:

[UIView animateWithDuration:2.0 animations:^{
  [self.collection reloadSections:indexSet];
}];
于 2018-04-15T15:33:18.197 回答
0

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

于 2015-02-03T08:44:24.130 回答