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

如何实现非滚动时UICollectionView选中单元格与邻格的额外间距?

实现UICollectionView选中单元格与相邻单元格的动态额外间距

要实现非滚动状态下选中单元格与相邻单元格的额外间距,确实需要自定义UICollectionViewFlowLayout子类,结合你已有的isScrollingInMotion状态标记来控制间距的应用时机,具体实现如下:

1. 自定义FlowLayout子类

创建继承自UICollectionViewFlowLayout的子类,用于动态调整选中项的相邻间距:

class CustomPhotoFlowLayout: UICollectionViewFlowLayout {
    var selectedIndexPath: IndexPath?
    var isScrollingInMotion = false
    let extraSpacing: CGFloat = 8 // 自定义额外间距值
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // 复制原布局属性,避免修改系统缓存
        guard let attributes = super.layoutAttributesForElements(in: rect)?.map({ $0.copy() as! UICollectionViewLayoutAttributes }),
              let selectedIndex = selectedIndexPath,
              !isScrollingInMotion else {
            return super.layoutAttributesForElements(in: rect)
        }
        
        // 获取选中项的布局属性
        guard let selectedAttrs = attributes.first(where: { $0.indexPath == selectedIndex }) else {
            return attributes
        }
        
        // 调整前一个单元格的间距
        let prevIndex = IndexPath(item: selectedIndex.item - 1, section: selectedIndex.section)
        if let prevAttrs = attributes.first(where: { $0.indexPath == prevIndex }) {
            prevAttrs.frame.origin.x = selectedAttrs.frame.origin.x - prevAttrs.frame.width - minimumInteritemSpacing - extraSpacing
        }
        
        // 调整后一个单元格的间距
        let nextIndex = IndexPath(item: selectedIndex.item + 1, section: selectedIndex.section)
        if let nextAttrs = attributes.first(where: { $0.indexPath == nextIndex }) {
            nextAttrs.frame.origin.x = selectedAttrs.frame.origin.x + selectedAttrs.frame.width + minimumInteritemSpacing + extraSpacing
        }
        
        return attributes
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true // 确保滚动过程中布局能实时更新
    }
}

2. 替换ViewController中的布局实例

在setupCollectionView方法中使用自定义布局:

private func setupCollectionView() {
    let layout = CustomPhotoFlowLayout()
    layout.scrollDirection = .horizontal
    layout.minimumInteritemSpacing = itemSpacing

    let sideInset = (view.bounds.width - 44) / 2 // 保持首尾项居中
    layout.sectionInset = UIEdgeInsets(top: 0, left: sideInset, bottom: 0, right: sideInset)
    collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    // ... 其他初始化代码
}

3. 同步状态并触发布局更新

在选中状态变化、滚动状态变化时,更新自定义布局的状态并触发重新布局:

// 选中单元格时更新布局状态
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return }
    layout.selectedIndexPath = indexPath
    layout.invalidateLayout()
}

// 滚动状态更新时同步布局状态
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    isScrollingInMotion = true
    guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return }
    layout.isScrollingInMotion = true
    layout.invalidateLayout()
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    isScrollingInMotion = false
    guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return }
    layout.isScrollingInMotion = false
    layout.invalidateLayout()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        isScrollingInMotion = false
        guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return }
        layout.isScrollingInMotion = false
        layout.invalidateLayout()
    }
}

关键说明

  • extraSpacing可以根据需求调整为你需要的额外间距值
  • 复制布局属性是为了避免修改系统缓存的布局数据,防止出现异常布局
  • 每次状态变化时调用invalidateLayout()是确保布局能重新计算并应用新的间距规则

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:50:01