如何使UICollectionView的中心单元与位于侧面的其他两个单元重叠?

时间:2016-09-08 12:29:43

标签: ios swift uicollectionview carousel

我正在制作一个集合视图来生成轮播效果。

我需要中心单元格与左侧和右侧的其他两个单元格重叠。中心单元需要始终位于顶部。

但是,当我尝试将侧面的细胞与中心的细胞重叠时,它不起作用。相反,右侧单元格(蓝色单元格)与中心单元格(黑色单元格)重叠,如下图所示。

enter image description here

用于此效果的代码如下:

以下是集合视图的视图控制器。

import UIKit

private let reuseIdentifier = "Cell"
let kRoomCellScaling: CGFloat = 0.6

class RoomsViewController: UICollectionViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    // This method sets up the collection view
    let layout = UPCarouselFlowLayout()
    layout.itemSize = CGSizeMake(250, 250)
    layout.scrollDirection = .Horizontal

    layout.sideItemAlpha = 1
    layout.sideItemScale = 0.8
    layout.spacingMode = UPCarouselFlowLayoutSpacingMode.overlap(visibleOffset: 60)

    collectionView?.setCollectionViewLayout(layout, animated: false)

}

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



// MARK: UICollectionViewDataSource

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}


override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of items
    return 3
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)



    // Configure the cell
    switch indexPath.row%3 {

    case 0:
        cell.backgroundColor = UIColor.redColor()
    case 1:
        cell.backgroundColor = UIColor.blackColor()
    case 2:
        cell.backgroundColor = UIColor.blueColor()

    default:
        break

    }

    return cell
}


}

以下是用于集合视图的流程布局。

import UIKit


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

public class UPCarouselFlowLayout: UICollectionViewFlowLayout {

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

    @IBInspectable public var sideItemScale: CGFloat = 0.6
    @IBInspectable public var sideItemAlpha: CGFloat = 0.6
    public var spacingMode = UPCarouselFlowLayoutSpacingMode.fixed(spacing: 40)

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


    override public func prepareLayout() {
        super.prepareLayout()

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

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

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

    private func updateLayout() {
        guard let collectionView = self.collectionView else { return }

        let collectionSize = collectionView.bounds.size
        let isHorizontal = (self.scrollDirection == .Horizontal)

        let yInset = (collectionSize.height - self.itemSize.height) / 2
        let xInset = (collectionSize.width - self.itemSize.width) / 2
        self.sectionInset = UIEdgeInsetsMake(yInset, xInset, yInset, xInset)

        let side = isHorizontal ? self.itemSize.width : self.itemSize.height
        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 = isHorizontal ? xInset : yInset
            self.minimumLineSpacing = inset - fullSizeSideItemOverlap
        }
    }

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

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

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

        let collectionCenter = isHorizontal ? collectionView.frame.size.width/2 : collectionView.frame.size.height/2
        let offset = isHorizontal ? collectionView.contentOffset.x : collectionView.contentOffset.y
        let normalizedCenter = (isHorizontal ? attributes.center.x : attributes.center.y) - offset

        let maxDistance = (isHorizontal ? self.itemSize.width : self.itemSize.height) + 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
        attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)

        return attributes
    }

    override public func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        guard let collectionView = collectionView where !collectionView.pagingEnabled,
            let layoutAttributes = self.layoutAttributesForElementsInRect(collectionView.bounds)
            else { return super.targetContentOffsetForProposedContentOffset(proposedContentOffset) }

        let isHorizontal = (self.scrollDirection == .Horizontal)

        let midSide = (isHorizontal ? collectionView.bounds.size.width : collectionView.bounds.size.height) / 2
        let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x : proposedContentOffset.y) + midSide

        var targetContentOffset: CGPoint
        if isHorizontal {
            let closest = layoutAttributes.sort { abs($0.center.x - proposedContentOffsetCenterOrigin) < abs($1.center.x - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes()
            targetContentOffset = CGPoint(x: floor(closest.center.x - midSide), y: proposedContentOffset.y)
        }
        else {
            let closest = layoutAttributes.sort { abs($0.center.y - proposedContentOffsetCenterOrigin) < abs($1.center.y - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes()
            targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - midSide))
        }

        return targetContentOffset
    }
}

那么,我怎样才能使中心单元始终与其他两个单元格重叠?

1 个答案:

答案 0 :(得分:7)

根据与中心的距离,您可以translate将您的商品设为较小的负z值。

替换此行:

attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)

let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.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

enter image description here

或者您可以根据与中心的距离围绕y轴旋转项目,并为transform.m34提供一个小的负值,以便它具有透视效果和更真实的外观。

替换此行:

attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)

let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.collectionView!.bounds.size)
let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect)
let currentAngle = dist / (CGRectGetWidth(visibleRect)/2)
var transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
transform.m34 = -1.0 / 1000
transform = CATransform3DRotate(transform, -currentAngle, 0, 1, 0)
attributes.transform3D = transform

enter image description here