You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技术求助:Bottom Sheet中Collection View单元格重叠动画实现

实现Bottom Sheet联动CollectionView的单元格重叠/展开动画

没问题,我来帮你搞定这个Bottom Sheet联动CollectionView的重叠动画效果!核心思路是根据Bottom Sheet的拖拽进度,动态调整CollectionView单元格的缩放、平移和层级,下面是具体的实现步骤:


1. 给你的Bottom Sheet类添加控制参数

首先在ScrollableBottomSheetViewController里添加几个控制动画的属性,方便你后续调整效果:

class ScrollableBottomSheetViewController: UIViewController {
    // 原有的属性保留...
    @IBOutlet var pageControl: UIPageControl!
    @IBOutlet var collection: UICollectionView!
    @IBOutlet var headerView: UIView!
    let fullView: CGFloat = 0
    var partialView: CGFloat { return UIScreen.main.bounds.height - 50 }
    let collectionMargin = CGFloat(32)
    let itemSpacing = CGFloat(10)
    let itemHeight = CGFloat(333)
    
    // 新增动画控制属性
    var currentDragProgress: CGFloat = 0.0 // 拖拽进度:0=完全收起,1=完全展开
    private let collapsedScale: CGFloat = 0.85 // 收起时单元格的缩放比例
    private let collapsedOffsetX: CGFloat = -20 // 收起时单元格的水平偏移量(控制重叠程度)
    
    // 原有的方法保留...
}

2. 监听Bottom Sheet的拖拽进度

找到你处理拖拽手势的方法(比如handlePanGesture),在里面计算拖拽进度,并触发CollectionView布局刷新:

@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
    let translation = gesture.translation(in: view)
    // 计算拖拽进度:从完全展开(progress=1)到完全收起(progress=0)
    let progress = 1 - (view.frame.minY - fullView) / (partialView - fullView)
    currentDragProgress = max(0, min(1, progress)) // 限制在0-1范围内
    
    // 保留你原来的Bottom Sheet拖拽位移逻辑(调整view的frame)
    // ...
    
    // 刷新CollectionView布局,触发动画
    collection.collectionViewLayout.invalidateLayout()
}

3. 自定义CollectionView布局类

创建一个自定义的UICollectionViewFlowLayout,用来动态调整每个单元格的显示状态:

class OverlappingCollectionLayout: UICollectionViewFlowLayout {
    // 弱引用关联Bottom Sheet控制器,获取拖拽进度
    weak var bottomSheetVC: ScrollableBottomSheetViewController?
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let baseAttributes = super.layoutAttributesForElements(in: rect),
              let sheetVC = bottomSheetVC else {
            return baseAttributes
        }
        
        let progress = sheetVC.currentDragProgress
        let currentPage = sheetVC.pageControl.currentPage
        
        // 遍历所有单元格属性,逐个调整
        return baseAttributes.map { attr in
            let copiedAttr = attr.copy() as! UICollectionViewLayoutAttributes
            let itemIndex = copiedAttr.indexPath.item
            
            if itemIndex != currentPage {
                // 非当前页的单元格:根据进度应用缩放+平移
                let scale = sheetVC.collapsedScale + (1 - sheetVC.collapsedScale) * progress
                let offsetX = sheetVC.collapsedOffsetX * (1 - progress)
                
                // 调整层级:当前页在最上层,其他页按顺序在后方
                copiedAttr.zIndex = currentPage - itemIndex
                // 应用变换
                var transform = CGAffineTransform(scaleX: scale, y: scale)
                transform = transform.translatedBy(x: offsetX, y: 0)
                copiedAttr.transform = transform
            } else {
                // 当前页的单元格:保持原始状态,层级最高
                copiedAttr.transform = .identity
                copiedAttr.zIndex = Int.max
            }
            return copiedAttr
        }
    }
    
    // 当CollectionView bounds变化时(比如Bottom Sheet拖拽),重新计算布局
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

4. 替换原有布局并关联控制器

在ScrollableBottomSheetViewController的viewDidLoad方法里,替换原来的默认布局为我们的自定义布局:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 初始化自定义布局
    let overlappingLayout = OverlappingCollectionLayout()
    overlappingLayout.bottomSheetVC = self
    // 配置布局参数(和你原来的设置保持一致)
    overlappingLayout.itemSize = CGSize(
        width: collection.bounds.width - collectionMargin * 2,
        height: itemHeight
    )
    overlappingLayout.sectionInset = UIEdgeInsets(
        top: 0,
        left: collectionMargin,
        bottom: 0,
        right: collectionMargin
    )
    overlappingLayout.minimumLineSpacing = itemSpacing
    collection.collectionViewLayout = overlappingLayout
    
    // 保留你原来的其他初始化代码(注册cell、设置分页、pageControl关联等)
    // ...
}

5. 处理分页切换时的动画

当用户通过pageControl切换页面时,要刷新布局确保新的当前页状态正确:

@IBAction func pageControlChanged(_ sender: UIPageControl) {
    let targetIndex = IndexPath(item: sender.currentPage, section: 0)
    collection.scrollToItem(at: targetIndex, at: .centeredHorizontally, animated: true)
    // 刷新布局,更新所有单元格的状态
    collection.collectionViewLayout.invalidateLayout()
}

调整效果小贴士

  • 你可以修改collapsedScale(比如改成0.9)来调整收起时单元格的大小
  • 修改collapsedOffsetX(比如改成-30)来调整重叠的程度
  • 如果你的CollectionView是垂直方向的,把平移的x轴改成y轴即可

内容的提问来源于stack exchange,提问作者Nasir Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:23:05