通过切换布局触发的动画 UICollectionView 单元格调整大小

Posted

技术标签:

【中文标题】通过切换布局触发的动画 UICollectionView 单元格调整大小【英文标题】:Animating UICollectionView cell resize triggered by switching layouts 【发布时间】:2013-11-20 04:12:46 【问题描述】:

我正在尝试在各种 UICollectionViewFlowLayout 子类之间制作动画。每个流布局指定不同的项目大小。一个显示一个包含 3 个项目的网格,另一个显示一个包含 2 个项目的网格,第三个显示一个只有 1 个项目的网格(看起来像 UITableView)。

我正在调用 setCollectionViewLayout:animated: 来执行此操作。单元格大小的动画效果完美。但是,子视图有点混乱。每个单元格包含一个图像和一个标签(暂时已删除标签)。我希望图像随着单元格大小的变化而动画其大小变化,标签也是如此。

如果我使用自动布局(还不是很擅长)来布局我的单元格的子视图,那么在动画开始时,图像的大小会调整为目标单元格框架。我了解 UIView 动画的工作原理,因为它们会立即更改动画属性,并且动画是在表示层中完成的,但我希望自动布局以某种方式工作。

如果我删除自动布局并在 initWithFrame 中执行以下操作:

[[self imageView] setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin];

然后图像子视图的大小动画效果非常好,但是在循环播放一些动画后,图像子视图的大小和位置在某些情况下不正确。

我尝试在 layoutSubviews 中布局子视图,但这与 AutoLayout 的结果相同。

我在 github 上看到的所有用于 UICollectionViewLayouts 之间动画的演示都只有一个彩色背景或一个填充整个单元格的子视图。有没有人设法让这个工作?

下面是正在发生的图片(红线是 UIImageView 的边框):

First Layout

Second Layout

Third Layout

Back to first layout, but subview frames are wrong

【问题讨论】:

我将在接下来的几天内对此进行试验。我相信,如果您的图像视图布局没有高度或宽度限制,并且将前导、尾随、顶部和底部空间约束到容器视图,那么您应该会得到想要的效果。 您好,我遇到了完全相同的问题。如果您找到了解决方案,请告诉我! 【参考方案1】:

要使用 AutoLayout,您需要在 UICollectionViewCell 子类中添加以下内容:

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes 
    [UIView animateWithDuration:animationDuration animations:^
        [self layoutIfNeeded];
    ];

默认动画持续时间约为 0.33 秒(根据实验确定)。

【讨论】:

我想在向上箭头上按 3 次,遗憾的是只允许按 3 次。 告诉我所有的团队成员来为 FFS 点赞! 拉德。对于它的价值,如果我只调用 layoutIfNeeded(即不包含在 animateWithDuration 块中,这也适用于动画布局到布局转换。【参考方案2】:

所以我采用的解决方案是关闭自动布局(正如 Marty 指出的那样),但我也删除了所有自动调整大小的蒙版。我在 UICollectionViewCell 子类中为要显示的每个 UICollectionViewFlowLayout 创建了三个不同的硬编码布局。

GSProductCollectionViewCell.m

-(void)setLayoutForWideLayoutWithDestinationSize:(CGSize)size

    [[self imageView] setFrame:CGRectMake( 5.0f, 5.0f, (int)(size.width * 0.3f), size.height - 10.0f)];

    [[self titleLabel] setFrame:CGRectMake( self.imageView.right + 5.0f, 5.0f, size.width - self.imageView.right - 15.0f, 25.0f)];
    [[self titleLabel] setFont:[UIFont fontWithName:self.titleLabel.font.fontName size:12.0f]];

    [[self titleBackgroundView] setFrame:self.titleLabel.frame];
    [[self titleBackgroundView] setAlpha:1.0f];

    [[self descriptionLabel] setAlpha:1.0f];
    [[self descriptionLabel] setFrame:CGRectMake( self.titleLabel.left,     self.titleLabel.bottom + 5.0f, self.titleLabel.width, 25.0f)];

     [...]


-(void)setLayoutForSqaureLayoutWithDestinationSize:(CGSize)size

    [[self imageView] setFrame:CGRectMake( 5.0f, 5.0f, size.width - 10.0f, size.height - 10.0f - 30.0f)];

    [[self titleLabel] setFrame:CGRectMake( 5.0f, size.height - 30.0f, size.width - 10.0f, 25.0f)];
    [[self titleLabel] setFont:[UIFont fontWithName:self.titleLabel.font.fontName size:10.0f]];

    [[self titleBackgroundView] setFrame:self.titleLabel.frame];
    [[self titleBackgroundView] setAlpha:0.0f];

    [[self descriptionLabel] setAlpha:0.0f];
    [[self descriptionLabel] centerView];

    [...]


-(void)setLayoutWithFrameSize:(CGSize)size forStyle:(GSProductLayoutStyle)layoutStyle

    switch (layoutStyle)
    
        case kGSProductLayoutStyleGrid3:
        case kGSProductLayoutStyleGrid2:
            [self setLayoutForSqaureLayoutWithDestinationSize:size];
            break;

        case kGSProductLayoutStyleList:
            [self setLayoutForWideLayoutWithDestinationSize:size];
            break;

        default:
            break;
    

然后当用户选择循环布局按钮时,我会前进到下一个布局并在我的 ViewController 中调用执行以下操作(有一个 UICollectionView):

-(void)setLayoutStyle:(GSProductLayoutStyle)layoutStyle

    if (_layoutStyle != layoutStyle)
    
        _layoutStyle = layoutStyle;

        UICollectionViewFlowLayout *layout;

        switch (_layoutStyle)
        
           case kGSProductLayoutStyleGrid3:
                layout = [GSProductCollectionViewGridLayout new];
                break;

            case kGSProductLayoutStyleGrid2:
                layout = [GSProductCollectionViewGridTwoLayout new];
                break;

            case kGSProductLayoutStyleList:
                layout = [GSProductCollectionViewListLayout new];
                break;

            default:
                layout = [GSProductCollectionViewGridLayout new];
                break;
        

        CGSize itemSize = layout.itemSize;

        [self setCollectionViewLayout:layout animated:YES];

        [[self visibleCells] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 

            [(GSProductCollectionViewCell*)obj setLayoutStyle:_layoutStyle];

            [UIView animateWithDuration:0.3f animations:^

                [(GSProductCollectionViewCell*)obj setLayoutWithFrameSize:itemSize forStyle:self.layoutStyle];

            ];
        ];

    

这使我可以完全控制每种 FlowLayout 子类类型的 Cell 中的每个布局。动画很流畅,我还可以淡出我不希望在特定布局中出现的任何视图。

这样的结果是 UICollectionView,它的行为很像 iPhone Ebay Apps 的网格视图行为,但在我看来更好。

抱歉,我无法粘贴更多代码或将其放在 GitHub 上,因为它来自客户端代码。 不过,我很乐意回答任何问题。

干杯, 布雷特

【讨论】:

【参考方案3】:

您好 Brett,在到处寻找答案后,我发现这对我有帮助:

UICollectionView cell subviews do not resize

我最终关闭了自动布局,只使用了 IB 的传统自动调整大小功能。

工作享受!希望对你有帮助!

编辑:抱歉,我刚刚意识到您是通过编程方式执行此操作的,您能否在完成块动画设置布局中调用重新加载数据来重绘您的单元格?按照这个:

UICollectionView cells, auto layout and collectionview layout changes

【讨论】:

以上是关于通过切换布局触发的动画 UICollectionView 单元格调整大小的主要内容,如果未能解决你的问题,请参考以下文章

在类之间切换使动画无法通过,只是跳到结束

MobX React 替换 componentWillReact() 以触发布局动画

动画切换 CollectionViewLayout

UICollectionView 上的动画自动布局约束更改

android界面切换增加了animation特效,但是没有实现动画效果(已经执行到setAni

jQuery基础(动画篇 animate,显示隐藏,淡入淡出,下拉切换)