iOS 在CollectionView上做展开收起动画

1.目标效果:

在一个collectionView上有一个cell, cell上有一个按钮点击收起/展开, 根据用户的操作展示不同的高度.

实现效果如下, 本来传的gif, 但是csdn一致提示违规, 传成静态, 剩下靠脑补:

 

2.遇到的问题:

一开始使用改动是reloadItemsAtIndexPaths 进行刷新, 出现了展开时下面的cell被遮挡, 收起时会有部分区域黑一下, 然后在出现收起的动画, 达不到目标效果

看下官方文档对reloadItemsAtIndexPaths的解释

Reloads just the items at the specified index paths.

Call this method to selectively reload only the specified items. This causes the collection view to discard any cells associated with those items and redisplay them.


重新加载指定数组内的indexPath的元素。

调用此方法可以选择性地仅重新加载指定的元素。这会导致collectionView视图取消与这些项关联的cell,并重新显示它们。

根据官方文档的注释和实际测试, 使用此方法只刷新一个分区数据, 系统会再次触发 - cellForItemAtIndexPath, 由于此时屏幕上已经有一个同类型的cell, 系统会在创建一个新的cell并添加到复用池中, 此时对旧的cell做任何动画都是无效的, 因为现在有一个更高层级的cell盖在上面.

3.解决方案: 

使用performBatchUpdates:completion:

Animates multiple insert, delete, reload, and move operations as a group. 

将多个插入、删除、重新加载和移动操作作为一个组设置动画。

此方法不会触发- cellForItemAtIndexPath, 所以不会重新创建cell, 对cell本身做的动画能够正常显示. 

同时, 此方法会触发- sizeForItemAtIndexPath, 保证cell本身的高度是正确的, collectionView的整体高度和下面的cell会自动下移完成动画.

4.具体写法:

总结一下在collectionView做动画的流程:

  1. 使用UIView的animationBlock指定动画时长, 把2-4放到block中执行
  2. 更新cell内部的数据
  3. 使用 performBatchUpdates:completion:  刷新collectionView上cell的高度
  4. 调用layout方法刷新UI, 调用collectionView 或者 cell都可以
// 展开收起动画, 
// 1.更新cell内部的数据;
// 2.调用刷新cell高度的方法;
// 3.collectionView进行layout
[UIView animateWithDuration:0.3 animations:^{

   [cell updateWithRowNode:rowNode];

   [self.collectionView performBatchUpdates:^{
    } completion:^(BOOL finished) {
    }];

    [self.collectionView layoutIfNeeded];
    [self.collectionView reloadData];

}];

最后放上代码, 有删减,

/// 点击展开/收起按钮
- (void)memberCenterStatusCell:(ULMemberCenterStatusCell *)cell didClickFold:(UIButton *)button rowNode:(ULTopOpenMemberRowNode *)rowNode {

    if (rowNode.uiType == ULTopOpenMemberRowNodeUITypeMember) {
        NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
        rowNode.isOpen = !rowNode.isOpen;
        ULMemberCenterSectionRowNode *sectionRowNode = [self.dataArray ul_safeObjectAtIndex:indexPath.section];
        sectionRowNode.itemSize = [rowNode currentSize];
        // 展开收起动画, 
        // 1.更新cell内部的数据;
        // 2.调用刷新cell高度的方法;
        // 3.collectionView进行layout
        [UIView animateWithDuration:0.3 animations:^{

            [cell updateWithRowNode:rowNode];

           [self.collectionView performBatchUpdates:^{

            } completion:^(BOOL finished) {

            }];

            [self.collectionView layoutIfNeeded];
            [self.collectionView reloadData];

        }];
    }

}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

    CGSize result = CGSizeZero;
    ULMemberCenterSectionRowNode *sectionRowNode = [self.dataArray ul_safeObjectAtIndex:indexPath.section];
     result = sectionRowNode.itemSize;
    return result;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *resultCell = nil;
    ULMemberCenterSectionRowNode *sectionRowNode = [self.dataArray ul_safeObjectAtIndex:indexPath.section];
    if (sectionRowNode.type == ULMemberCenterSectionRowNodeTypeOpenMember) {
        ULMemberCenterStatusCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[ULMemberCenterStatusCell reuseIdentifier] forIndexPath:indexPath];
        ULTopOpenMemberRowNode *openMemberRowNode = [sectionRowNode.rowNodeArray ul_safeObjectAtIndex:indexPath.item];
        cell.delegate = self;
        [cell updateWithRowNode:openMemberRowNode];
        resultCell = cell;
    } 
     return resultCell;

}

还有2个坑, 由于collectionview内部视图的高度发生了变化, 本来有的cell是不能漏出的, 在收起的时候, 这个cell变的可以出现了, 因此出现了2个坑.

第一个坑是有概率出现一个crash,

index path为4-0的 LayoutAttributes 从 0x106d54940 变成了 0x106d54830, 而没有调用 invalidating the layout. 

简略信息:

layout attributes for supplementary item at index path (<NSIndexPath: 0xee9228e53cb9843e> {length = 2, path = 4 - 0}) changed from <ULMemberCenterLayoutAttributes: 0x106d54940>  to <ULMemberCenterLayoutAttributes: 0x106d54830>  without invalidating the layout

完整内容如下:

layout attributes for supplementary item at index path (<NSIndexPath: 0xee9228e53cb9843e> {length = 2, path = 4 - 0}) changed from <ULMemberCenterLayoutAttributes: 0x106d54940> index path: (<NSIndexPath: 0xee9228e53cb9853e> {length = 2, path = 5 - 0}); element kind: (ULMemberCenterFlowLayoutSectionBackground); frame = (0 1355; 375 320); zIndex = -1; to <ULMemberCenterLayoutAttributes: 0x106d54830> index path: (<NSIndexPath: 0xee9228e53cb9843e> {length = 2, path = 4 - 0}); element kind: (ULMemberCenterFlowLayoutSectionBackground); frame = (0 990; 375 300); zIndex = -1; without invalidating the layout

看的一头雾水, 而且bugly上出现次数不多, 好在stackoverflow上有个解答, 只需要设置禁止cell的预加载即可.

if (@available(iOS 10.0, *)) {
    self.collectionView.prefetchingEnabled = NO;
}

另一个设置 - shouldInvalidateLayoutForBoundsChange , 亲测对我的场景无效, 所以也没有加. 

https://stackoverflow.com/questions/19207924/uicollectionview-exception-in-uicollectionviewlayoutattributes-from-ios7

1.在iOS12上

2.单独刷新某个cell

3.通过继承UICollectionViewFlowLayout自定义FlowLayout

满足以上请求也有概率出现crash,堆栈和上面一致。

[self.collectionView reloadItemsAtIndexPaths:@[indexPath]];

🔽🔽🔽 , 改成这样之后不会出现crash了

[self.collectionView reloadData];

第二个坑是, 收起动画的时候, 如果屏幕底部的cell没有渲染, 那么系统在做收起动画的时候, 底部的cell是直接出现在结束位置的, 没有动画,

注意看最底部的3个cell, 在收起动画的时候, 是直接出现在结束位置的, 因为在顶部展开状态下, 这3个cell根本没有创建, 收起的时候, 系统才创建这3个cell, 而且系统创建好直接放到的cell的结束位置.

知道了原因, 修改的方法也有了:

本来collectionView的大小和父视图是一致的, 但是希望collectionView多向下渲染100像素(收起/展开状态下顶部cell的高度差距),  这样在收起动画的时候, 其实下面的100像素上已经有这3个cell了, 这3个cell也能跟随其他cell一起向上位移. 由于collectionView的高度多了100, 所以也需要配合contentInset的底部额外多出100像素,

[self.collectionView makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.right.equalTo(0);
    make.height.equalTo(self.mas_height).offset(100);
}];
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 100, 0);

 

正常情况下, 最底部的3个cell是不会创建的, 但是由于collectionView的高度变高, 底部的cell已经被提前创建好, 只是用户看不到而已, 

经过这样的奇技淫巧, 动画看上去和谐多了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOSCollectionView是一种强大的界面展示控件,它允许我们展示多个横向或纵向滚动的项。而Section Header是CollectionView中的一个重要组成部分,它可以用来展示每个分区的标题或其他相关信息。 在iOS中使用CollectionView的时候,我们可以通过实现UICollectionViewDelegateFlowLayout协议来设置每个分区的大小、间距等布局属性。而Section Header则需要通过实现UICollectionViewDelegate的collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法来自定义。 首先,我们需要创建一个UICollectionReusableView类的子类作为Section Header的视图。然后,在collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法中,我们需要判断elementKind是否为UICollectionElementKindSectionHeader,并根据需要从重用队列中获取Section Header的视图对象。 接下来,我们可以设置Section Header的标题、背景色、字体颜色等视觉效果。例如,我们可以使用UILabel来展示标题,并通过设置UILabel的text属性来显示每个分区的标题。如果需要更加丰富的自定义效果,我们还可以使用自定义的视图来展示Section Header。 最后,我们需要在UICollectionViewFlowLayout中设置sectionHeadersPinToVisibleBounds属性为true,以便在滚动时固定Section Header的位置。这样,当用户滚动CollectionView时,Section Header会始终显示在顶部,增加了用户的使用体验。 综上所述,iOSCollectionView提供了强大的支持来展示多个滚动项,而Section Header则可以用来展示每个分区的相关信息。通过实现UICollectionViewDelegate的collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法,我们可以自定义Section Header的视图,并通过UICollectionViewFlowLayout来设置其布局等属性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值