UICollectionView:使用动画和自定义布局调整单元格大小

Posted

技术标签:

【中文标题】UICollectionView:使用动画和自定义布局调整单元格大小【英文标题】:UICollectionView: Resizing cells with animations and custom layouts 【发布时间】:2021-01-27 11:00:09 【问题描述】:

我有一个带有自定义布局和可区分数据源的集合视图。我想用动画调整单元格的大小。

使用自定义布局:看起来单元格的内容在动画期间缩放到新的大小。只有当单元格达到其最终大小时,才会最终重绘(参见下面的动画)

使用库存流布局,子视图在动画过程中的布局正确:标签保持在顶部,没有调整大小,开关也一样。

所以我认为问题出在布局实现中,可以在布局中添加什么以允许在动画期间进行正确布局?

说明

本示例中使用的自定义布局是FSQCollectionViewAlignedLayout,我也使用我在代码中使用的自定义获得了它。我注意到在某些情况下,始终返回相同的布局属性对象很重要,FSQCollectionViewAlignedLayout 不会这样做。我修改为不返回副本,结果是一样的。

对于单元格(在 xib 中定义),单元格的contentMode 及其contentView 设置为.top。单元格的 contentView 包含 2 个子视图:一个标签(在 layoutSubviews 中布置)和一个开关(布置有约束,但它的存在对动画没有影响)。

控制器的代码是:

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout 
    enum Section 
        case main
    
    @IBOutlet weak var collectionView : UICollectionView!
    var layout = FSQCollectionViewAlignedLayout()
    var dataSource : UICollectionViewDiffableDataSource<Section, String>!
    var cellHeight : CGFloat = 100

    override func loadView() 
        super.loadView()
        
        layout.defaultCellSize = CGSize(width: 150, height: 100)
        collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
        
        dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider:  cv, indexPath, item in
            guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else  return nil 
            cell.label.text = item
            return cell
        )
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections([.main])
        snapshot.appendItems(["1", "2"], toSection: .main)
        dataSource.apply(snapshot)
    
    
    override func viewDidLoad() 
        super.viewDidLoad()
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: 150, height: cellHeight)
    
    
    @IBAction func resizeAction(_ sender: Any) 
        if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout 
            UIView.animate(withDuration: 0.25) 
                layout.defaultCellSize.height += 50
                let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
                invalidation.invalidateItems(at: [[0, 0], [0, 1]])
                layout.invalidateLayout(with: invalidation)
                self.view.layoutIfNeeded()
            
        
        if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout 
            UIView.animate(withDuration: 0.25) 
                self.cellHeight += 50
                let invalidation = UICollectionViewFlowLayoutInvalidationContext()
                invalidation.invalidateItems(at: [[0, 0], [0, 1]])
                layout.invalidateLayout(with: invalidation)
                self.view.layoutIfNeeded()
            
        
    


class Cell : UICollectionViewCell 
    @IBOutlet weak var label: UILabel!
    
    override func layoutSubviews() 
        super.layoutSubviews()
        label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
    

【问题讨论】:

【参考方案1】:

我在这个问题上花了开发者支持票。答案是不支持重新加载单元格时的自定义动画。

因此,作为一种解决方法,我最终创建了新的单元格实例,并将其添加到视图层次结构中,然后添加自动布局约束,以便它们与正在调整大小的单元格完全重叠。然后在这些新添加的单元格上运行动画。在动画结束时,这些单元格被移除。

【讨论】:

以上是关于UICollectionView:使用动画和自定义布局调整单元格大小的主要内容,如果未能解决你的问题,请参考以下文章

动画后 UICollectionViewCell 位置错误

使用 UICollectionView 自定义发送消息动画,如 iMessage 发送消息

自定义布局中约束更改的动画 UICollectionView 项目

2021-09-26 WPF上位机 46-路径动画和自定义帧动画

自定义 UICollectionView 单元格滑动动画

如何更改自定义 UICollectionView 布局引擎中的默认布局动画曲线?