MoviaBox/Thimra/Base/View/SPZoomCollectionViewLayout.swift
2025-04-22 15:21:12 +08:00

108 lines
4.3 KiB
Swift

//
// SPZoomCollectionViewLayout.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPZoomCollectionViewLayout: UICollectionViewFlowLayout {
let minimumScale: CGFloat = 0.08 //
private(set) var currentIndexPath: IndexPath = IndexPath(row: 0, section: 0)
private var cellWidth: CGFloat {
return itemSize.width + minimumLineSpacing
}
override func prepare() {
super.prepare()
scrollDirection = .horizontal
self.collectionView?.decelerationRate = .fast
let screenWidth = UIScreen.main.bounds.size.width
let insetLeft = (screenWidth - self.itemSize.width) / 2
collectionView?.contentInset = UIEdgeInsets(top: 0, left: insetLeft, bottom: 0, right: insetLeft)
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
guard let layoutAttributes = layoutAttributesForElements(in: proposedRect) else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
let horizontalCenterX = proposedContentOffset.x + collectionView.bounds.width / 2
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
// ydLog(message: "offsetAdjustment = \(offsetAdjustment)")
// ydLog(message: "horizontalCenterX = \(horizontalCenterX)")
var currentIndexPath: IndexPath = IndexPath(row: 0, section: 0)
for attributes in layoutAttributes {
let itemHorizontalCenterX = attributes.center.x
let distance = itemHorizontalCenterX - horizontalCenterX
// ydLog(message: "distance = \(distance)")
//
if abs(distance) < abs(offsetAdjustment) {
offsetAdjustment = distance
currentIndexPath = attributes.indexPath
}
}
self.currentIndexPath = currentIndexPath
let point = CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
// ydLog(message: "proposedContentOffset = \(proposedContentOffset)")
// ydLog(message: "offsetAdjustment = \(offsetAdjustment)")
// ydLog(message: "point = \(point)")
// ydLog(message: "currentIndex = \(currentIndex)")
return point
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let attributes = super.layoutAttributesForElements(in: rect)?.compactMap { $0.copy() as? UICollectionViewLayoutAttributes }
for attribute in attributes ?? [] {
let distance = visibleRect.midX - attribute.center.x
let normalizedDistance = distance / (collectionView.bounds.width * 0.5)
let zoom = 1 - abs(normalizedDistance) * minimumScale
//
let scaleTransform = CGAffineTransform(scaleX: zoom, y: zoom)
//
let rotationAngle = kSPAngleToRadians(angle: -normalizedDistance * 4)
let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle)
//
let combinedTransform = rotationTransform.concatenating(scaleTransform)
//
attribute.transform = combinedTransform
var alpha = 1.8 - abs(normalizedDistance)
if alpha < 0 {
alpha = 0
} else if alpha > 1 {
alpha = 1
}
attribute.alpha = alpha
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}