如何在UICollectionView滚动时为即将居中的UICollectionViewCell应用CGAffineTransform
实现UICollectionView滚动时中心Item动态放大效果
嘿,我完全懂你想要的那种顺滑效果——水平滚动菜单里,靠近中心的项目慢慢放大,居中时达到最大尺寸,离开中心时再平滑缩小回去。我来帮你完善逻辑,覆盖滚动过程中、停止时以及反向滚动的所有场景:
1. 先做好基础布局配置
首先得让UICollectionView的布局支持item滚动到中心,首尾item也能正常居中显示:
// 初始化流式布局 let flowLayout = UICollectionViewFlowLayout() flowLayout.itemSize = CGSize(width: 120, height: 180) // 按需调整尺寸 flowLayout.minimumLineSpacing = 20 flowLayout.scrollDirection = .horizontal // 设置内边距,让首尾item也能滚动到屏幕中心 let sideInset = (view.bounds.width - flowLayout.itemSize.width) / 2 collectionView.contentInset = UIEdgeInsets(top: 0, left: sideInset, bottom: 0, right: sideInset) collectionView.decelerationRate = .fast // 提升滚动顺滑度 collectionView.showsHorizontalScrollIndicator = false
2. 核心滚动监听与缩放逻辑
在你的UICollectionView代理(或ViewController)里实现滚动监听,实时计算每个item和中心的距离,动态调整缩放比例:
// 可自定义的缩放参数 private let maxCenterScale: CGFloat = 1.2 // 中心item的最大缩放倍数 private let normalScale: CGFloat = 0.9 // 非中心item的基础缩放倍数 private let scaleEffectRange: CGFloat = 150 // 超出这个距离就不再缩放(和item尺寸/间距匹配) func scrollViewDidScroll(_ scrollView: UIScrollView) { updateItemScaleTransforms() } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { // 让滚动停止时自动居中到最近的item let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout let itemTotalWidth = layout.itemSize.width + layout.minimumLineSpacing let targetOffsetX = round(targetContentOffset.pointee.x / itemTotalWidth) * itemTotalWidth targetContentOffset.pointee.x = targetOffsetX // 延迟更新确保item完全居中,避免动画卡顿 DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { self.updateItemScaleTransforms() } } private func updateItemScaleTransforms() { let screenCenterX = collectionView.contentOffset.x + collectionView.bounds.width / 2 for cell in collectionView.visibleCells { guard let _ = collectionView.indexPath(for: cell) else { continue } // 计算cell中心与屏幕可视区域中心的水平距离 let cellCenterX = cell.center.x let distanceToCenter = abs(screenCenterX - cellCenterX) // 根据距离动态计算缩放比例:越靠近中心,缩放越大 var currentScale = max(normalScale, maxCenterScale - (distanceToCenter / scaleEffectRange)) // 限制缩放范围,避免超出预设值 currentScale = min(maxCenterScale, max(normalScale, currentScale)) // 应用缩放变换,锚点设为中心保证缩放时位置不变 cell.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) cell.transform = CGAffineTransform(scaleX: currentScale, y: currentScale) } }
3. 额外优化建议
- 减少重复计算:给自定义cell加个
currentScale属性,只有当计算出的缩放比例和当前值不同时,再更新transform,提升性能; - 初始状态处理:在
collectionView(_:willDisplay:forItemAt:)里给cell初始化transform为normalScale,避免初始显示异常; - 更顺滑的动画:如果觉得实时缩放有点生硬,可以给transform变化加个短时长动画:
UIView.animate(withDuration: 0.1) { cell.transform = CGAffineTransform(scaleX: currentScale, y: currentScale) }
(注:scrollViewDidScroll调用频繁,短时长动画不会造成性能问题)
效果说明
- 滚动过程中,所有可见item会根据和中心的距离实时调整缩放比例,越靠近中心尺寸越大;
- 松手后,collectionView会自动滚动到最近的item中心,同时该item平滑放大到最大比例;
- 反向滚动时无需额外处理,因为我们是根据距离绝对值计算缩放比例的,方向不影响逻辑。
这样就能完美实现你想要的“即将滚动至中心时放大,移出时缩小”的效果啦!
内容的提问来源于stack exchange,提问作者Yannis P.
相关产品推荐
相关产品推荐

