在选择时为 UICollectionView 单元格设置动画

Posted

技术标签:

【中文标题】在选择时为 UICollectionView 单元格设置动画【英文标题】:Animate a UICollectionView cell on selection 【发布时间】:2013-02-04 19:38:05 【问题描述】:

我在UICollectionView 中有一个基本网格。这是一个简单的 2 列多行布局,使用 UICollectionViewDelegateFlowLayout。选择一个单元格后,我想调暗背景,将单元格浮动到屏幕中心,然后根据所选单元格进行工作流。我对UICollectionViews 还很陌生,我不确定解决这个问题的最佳方法。

我是否应该在选择单元格时使用 UICollectionView 的自定义布局?

或者有没有一种方法可以让选定的单元格动画化而无需创建新的布局

如果有人能让我朝着正确的方向前进,我想我会很好地研究如何执行它。

【问题讨论】:

【参考方案1】:

在尝试了几个(而且相当老套)的解决方案后,我通过编写自己的 UICollectionView 布局解决了这个问题。我尝试过的以前的解决方案:

UICollectionView 之上添加一个自定义 UIView,并尝试通过覆盖它、调暗背景和为新 UIView 设置动画来伪造原始单元格的移动 在不创建全新布局的情况下为单元格设置动画

我试图避免它,但在我开始编写它之后,它比我最初想象的要痛苦得多。

这是我的代码,任何有兴趣的人:

.h:

@interface FMStackedLayout : UICollectionViewLayout

@property(nonatomic,assign) CGPoint     center;
@property(nonatomic,assign) NSInteger   cellCount;

-(id)initWithSelectedCellIndexPath:(NSIndexPath *)indexPath;

@end

.m:

#define ITEM_WIDTH  128.0f
#define ITEM_HEIGHT 180.0f

static NSUInteger       const RotationCount         = 32;
static NSUInteger       const RotationStride        = 3;
static NSUInteger       const PhotoCellBaseZIndex   = 100;

@interface FMStackedLayout ()

@property(strong,nonatomic) NSArray     *rotations;
@property(strong,nonatomic) NSIndexPath *selectedIndexPath;

@end

@implementation FMStackedLayout

#pragma mark - Lifecycle
-(id)initWithSelectedCellIndexPath:(NSIndexPath *)indexPath
    self = [super init];
    if (self) 
        self.selectedIndexPath = indexPath;
        [self setup];
    
    return self;


-(id)initWithCoder:(NSCoder *)aDecoder
    self = [super init];
    if (self)
        [self setup];
    
    return self;


-(void)setup
    NSMutableArray *rotations = [NSMutableArray arrayWithCapacity:RotationCount];
    CGFloat percentage = 0.0f;

    for (NSUInteger i = 0; i < RotationCount; i++) 
        // Ensure that each angle is different enough to be seen
        CGFloat newPercentage = 0.0f;
        do 
            newPercentage = ((CGFloat)(arc4random() % 220) - 110) * 0.0001f;
         while (fabsf(percentage - newPercentage) < 0.006);

        percentage = newPercentage;

        CGFloat angle = 2 * M_PI * (1.0f + percentage);
        CATransform3D transform = CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
        [rotations addObject:[NSValue valueWithCATransform3D:transform]];
    

    self.rotations = rotations;


#pragma mark - Layout
-(void)prepareLayout
    [super prepareLayout];

    CGSize size = self.collectionView.frame.size;
    self.cellCount = [self.collectionView numberOfItemsInSection:0];
    self.center = CGPointMake(size.width / 2.0, size.height / 2.0);


-(CGSize)collectionViewContentSize
    return self.collectionView.frame.size;


-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attributes.size = CGSizeMake(ITEM_WIDTH, ITEM_HEIGHT);
    attributes.center = self.center;
    if (indexPath.item == self.selectedIndexPath.item) 
        attributes.zIndex = 100;
    else
        attributes.transform3D = [self transformForPersonViewAtIndex:indexPath];
    

    return attributes;


-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    NSMutableArray *attributes = [NSMutableArray array];
    for (NSInteger i = 0; i < self.cellCount; i++) 
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i
                                                     inSection:0];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    
    return attributes;


#pragma mark - Private
-(CATransform3D)transformForPersonViewAtIndex:(NSIndexPath *)indexPath
    NSInteger offset = (indexPath.section * RotationStride + indexPath.item);
    return [self.rotations[offset % RotationCount] CATransform3DValue];


@end

然后,在你的 UICollectionView 上调用

MyLayout *stackedLayout = [[MyLayout alloc] initWithSelectedCellIndexPath:indexPath];
[stackedLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:stackedLayout
                                    animated:YES];

单元格之间的动画将由自定义布局处理。

【讨论】:

这不是答案。看起来您有答案,但没有兴趣分享。 @Mark Struzinski 你能分享你的解决方案吗? 这是一段相当长的代码,但我并没有因为任何特殊原因而退缩。我已经把它贴在上面给任何有兴趣的人。不知道为什么这里有反对票。最终解决方案使用了 Apple 文档中详细记录的方法:developer.apple.com/library/ios/#documentation/uikit/reference/…。我一直在努力避免它,因为它的工作量很大。 你能控制动画的持续时间吗?【参考方案2】:

似乎是一个多部分的问题,所以我会回答一部分。

    UICollectionviewCells 不要在highlightselection上自动调整。它只会在单元格突出显示或选中时告诉您,但有一个例外:

在大多数情况下,collection view 只修改 一个单元格,表明它被选中或突出显示;它不是 更改单元格的视觉外观,但有一个例外。如果一个 单元格的selectedBackgroundView 属性包含一个有效视图,即 集合视图在单元格突出显示时显示该视图或 已选中。

否则,您必须手动进行视觉突出显示。通常通过调整整个单元格的.alpha 属性,或使用以下一种或多种方法替换单元格本身中背景UIImageView.image 属性,这些方法可在&lt;UICollectionViewDelegate&gt; 协议中访问:

collectionView:didDeselectItemAtIndexPath:
collectionView:didHighlightItemAtIndexPath:
collectionView:didSelectItemAtIndexPath:
collectionView:didUnhighlightItemAtIndexPath:
collectionView:shouldDeselectItemAtIndexPath:
collectionView:shouldHighlightItemAtIndexPath:
collectionView:shouldSelectItemAtIndexPath:

至于你的其他问题,我希望我能提供更多帮助,但我对自定义视图动画不太熟练。

【讨论】:

嗨。我已经尝试从 collectionView:didSelectItemAtIndexPath: 中制作动画。不过,根据问题,我想将单元格实际移动到屏幕中心以专注于它。我现在想知道这是否意味着我应该使用模态视图控制器并做一些事情来使它看起来单元格正在动画。

以上是关于在选择时为 UICollectionView 单元格设置动画的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionVIew:在滚动时为单元格设置动画

UICollectionVIew:在视图滚动时为单元格设置动画

UIViewController 在加载时为所有单元格调用 sizeForItemAtIndexPath

在 UICollectionView 中为特定单元格设置动画

在方向更改时为 UICollectionView 设置动画

UICollectionView 点击选择多个单元格