滚动后如何在 collectionView 单元格中居中微调样式的卡片视图?

问题描述 投票:0回答:1

我有一个像视差一样滚动的 collectionView,但它的活动可见单元格位于上一个和下一个单元格的顶部。但是,问题是当我从第一个单元格(黑色)滚动到左/右时,它会自动显示第 4 个单元格(青色)或从第 4 个(青色)单元格滚动到第一个(黑色)单元格,但我想要的是它应该滚动 1一次一个细胞(逐个细胞)。

形象化的例子是:

我正在使用以下逻辑:

extension UIScrollView {
var visibleRect: CGRect {
    CGRect(origin: contentOffset, size: bounds.size)
   }
 }

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

@IBOutlet private var collectionView: UICollectionView!

private var images = [
    UIColor.black,
    UIColor.purple,
    UIColor.yellow,
    UIColor.cyan,
    UIColor.red,
    UIColor.blue
]

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.register(
        UINib(
            nibName: "TutorialCollectionViewCell",
            bundle: nil),
        forCellWithReuseIdentifier: "TutorialCollectionViewCell")

    collectionView.delegate = self
    collectionView.dataSource = self

    let layout = CarouselFlowLayout()
    layout.itemSize = CGSize(width: 350, height: 350)
    layout.scrollDirection = .horizontal

    layout.sideItemAlpha = 0.8
    layout.sideItemScale = 0.8
    layout.spacingMode = CarouselFlowLayoutSpacingMode.overlap(visibleOffset: collectionView.frame.width * 0.5)
    collectionView?.setCollectionViewLayout(layout, animated: false)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return images.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TutorialCollectionViewCell", for: indexPath as IndexPath) as? TutorialCollectionViewCell else { return TutorialCollectionViewCell() }

    cell.set(item: (images[indexPath.row]))
    cell.layer.cornerRadius = 8.0
    return cell
  }
}

public enum CarouselFlowLayoutSpacingMode {
  case fixed(spacing: CGFloat)
  case overlap(visibleOffset: CGFloat)
}

class CarouselFlowLayout: UICollectionViewFlowLayout {

 struct LayoutState {
    var size: CGSize
    var direction: UICollectionView.ScrollDirection
    func isEqual(otherState: LayoutState) -> Bool {
        return CGSizeEqualToSize(self.size, otherState.size) && self.direction == otherState.direction
    }
}

var sideItemScale: CGFloat = 0.6
var sideItemAlpha: CGFloat = 0.6
var spacingMode = CarouselFlowLayoutSpacingMode.fixed(spacing: 90)

private var state = LayoutState(size: CGSizeZero, direction: .horizontal)

override func prepare() {
    super.prepare()

    let currentState = LayoutState(size: self.collectionView!.bounds.size, direction: self.scrollDirection)

    if !self.state.isEqual(otherState: currentState) {
        self.setupCollectionView()
        self.updateLayout()
        self.state = currentState
    }
}

private func setupCollectionView() {
    guard let collectionView = self.collectionView else { return }
    if collectionView.decelerationRate != .fast {
        collectionView.decelerationRate = .fast
    }
}

private func updateLayout() {
    guard let collectionView = self.collectionView else { return }
    let collectionSize = collectionView.bounds.size
    let yInset = (collectionSize.height - self.itemSize.height) / 2
    let xInset = (collectionSize.width - self.itemSize.width) / 2
    self.sectionInset = UIEdgeInsets(top: yInset, left: xInset, bottom: yInset, right: xInset)

    let side = self.itemSize.width
    let scaledItemOffset =  (side - side * self.sideItemScale) / 2
    switch self.spacingMode {
    case .fixed(let spacing):
        self.minimumLineSpacing = spacing - scaledItemOffset
    case .overlap(let visibleOffset):
        let fullSizeSideItemOverlap = visibleOffset + scaledItemOffset
        let inset = xInset
        self.minimumLineSpacing = inset - fullSizeSideItemOverlap
    }
}

override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    return true
}

override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    guard let superAttributes = super.layoutAttributesForElements(in: rect),
          let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes]
    else { return nil }
    return attributes.map({ self.transformLayoutAttributes(attributes: $0) })
}

private func transformLayoutAttributes(attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    guard let collectionView = self.collectionView else { return attributes }

    let collectionCenter = collectionView.frame.size.width / 2
    let offset = collectionView.contentOffset.x
    let normalizedCenter = attributes.center.x - offset

    let maxDistance = self.itemSize.width + self.minimumLineSpacing
    let distance = min(abs(collectionCenter - normalizedCenter), maxDistance)
    let ratio = (maxDistance - distance) / maxDistance

    let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha
    let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale
    attributes.alpha = alpha

    let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
    let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect)
    var transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
    transform = CATransform3DTranslate(transform, 0, 0, -abs(dist / 1000))
    attributes.transform3D = transform
    return attributes
}

override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    guard let collectionView = self.collectionView else {
            let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
            return latestOffset
        }

        let pageWidth = self.itemSize.width + self.minimumInteritemSpacing
        let approximatePage = collectionView.contentOffset.x/pageWidth
        let currentPage = velocity.x == 0 ? round(approximatePage) : (velocity.x < 0.0 ? floor(approximatePage) : ceil(approximatePage))
        let flickVelocity = velocity.x * 0.3
        let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
        let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left
        return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
   }
}
ios swift parallax uicollectionviewlayout
1个回答
0
投票

你混淆了

minimumLineSpacing
minimumInteritemSpacing
targetContentOffset(forProposedContentOffset:withScrollingVelocity)

尝试计算

pageWidth
为:

let pageWidth = self.itemSize.width + self.minimumLineSpacing
© www.soinside.com 2019 - 2024. All rights reserved.