// // KMAdvertisementFlowLayout.swift // KMAdvertisement_iOS // // Created by lizhe on 2022/12/2. // import UIKit class KMAdvertisementFlowLayout: UICollectionViewFlowLayout { ///单元格的宽度 private let K_CELL_WIDTH: CGFloat = 260 override init() { super.init() itemSize = CGSize(width: K_CELL_WIDTH, height: 360) //cell的大小自定义 scrollDirection = .horizontal minimumLineSpacing = 0 } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } /// 不想频繁去取值, 直接提出来一个属性, 然后直接计算出半宽 var collectionViewFrameWidth: CGFloat = 0 { didSet { collectionViewFrameWidthHalf = collectionViewFrameWidth / 2 } } var collectionViewFrameWidthHalf: CGFloat = 0 var collectionViewFrameHeight: CGFloat = 0 /// 不想每次都去创建这个属性,所以提出来 var visibleRect = CGRect.zero /// 把相关属性初始化放到一起,初始化一次就可以了,在 func needViewInfo() { if collectionViewFrameWidth != collectionView?.frame.width { collectionViewFrameWidth = collectionView?.frame.width ?? 0 let offsetXY = (collectionViewFrameWidth - K_CELL_WIDTH) / 2 //在滑动到左右两端的时候, 需要让头尾的cell居中显示,那就需要设置边缘距离, 这个距离就是 屏幕宽-cell宽 除以 2 sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) } if collectionViewFrameHeight != collectionView?.frame.height { collectionViewFrameHeight = collectionView?.frame.height ?? 0 } } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { //先做一下相关属性的初始化 needViewInfo() //可视区域 visibleRect.origin = collectionView?.contentOffset ?? CGPoint.zero visibleRect.size = collectionView?.frame.size ?? CGSize.zero //屏幕中线 let centerX = visibleRect.midX ///获取到可以看见的cell的数组,看不见的cell就不要了 如果这里传入 rect, 那获取到的就是所有缓存中的cell, 并不是全部的总cell个数 let array = super.layoutAttributesForElements(in: visibleRect) ?? [] for attributes in array { // 随着滑动, centerX 是一直都在改变的, 但是每个cell的 attributes.center.x 是不变的 let distance = abs(centerX - attributes.center.x) //for循环中当前cell和可见区域的中心点的距离 let scaleRate = distance / (collectionView?.bounds.width ?? 0) // 使用 cos 函数,把缩放的比值转换成一个 1 以为的数值 // 在 scaleRate 后面乘上一个数,就可以控制卡片缩放的剧烈程度 let scale = abs(cos(scaleRate * 0.4)) //scaleRate == 0, 越是接近 centerX 的那个 attribute, 他的缩放就越接近于1(就是cell原来的大小), 可以直接使用这样的缩放,这样的缩放,卡片的大小变化不剧烈 //let scale = abs(cos(scaleRate)) attributes.transform3D = CATransform3DMakeScale(scale, scale, 1) } return array } override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { //被推出屏幕的长度 + 屏幕宽度的一半 let horizontalCenter = proposedContentOffset.x + collectionViewFrameWidthHalf //获取一个当前CollectionView的尺寸大小,然后这个尺寸的 X 就是滑动停止时候的偏移 let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: collectionViewFrameWidth, height: collectionViewFrameHeight) //用这个方法取得当前能看到的所有的cell let array = super.layoutAttributesForElements(in: targetRect) ?? [] //记录每次遍历取到的最小的距离中心的距离 var nearestCenterOne = CGFloat(MAXFLOAT) //记录距离中心最近的那个layoutitem的中心点x var shouldBeX: CGFloat = 0 //遍历当前数组里面的元素,找到里中心点最近的那个元素,把这个元素的中心位置拿到,然后返回给函数 for layoutAttributes in array { let currentX = layoutAttributes.center.x let currentDistance = abs(currentX - horizontalCenter) if currentDistance < nearestCenterOne { nearestCenterOne = currentDistance shouldBeX = currentX } } let X = shouldBeX - collectionViewFrameWidthHalf return CGPoint(x: X , y: proposedContentOffset.y) } }