我正在制作一个集合视图来生成轮播效果.
我需要中心单元格与左侧和右侧的其他两个单元格重叠.中心单元需要始终位于顶部.
但是,当我尝试将侧面的细胞与中心的细胞重叠时,它不起作用.相反,右侧单元格(蓝色单元格)与中心单元格(黑色单元格)重叠,如下图所示.
以下是集合视图的视图控制器.
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,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 } }
那么,我怎样才能使中心单元始终与其他两个单元格重叠?
解决方法
您可以根据距离中心的距离将项目转换为较小的负z值.
替换此行:
attributes.transform3D = CATransform3DScale(CATransform3DIdentity,1)
同
let visibleRect = CGRect(origin: self.collectionView!.contentOffset,size: self.collectionView!.bounds.size) let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect) var transform = CATransform3DScale(CATransform3DIdentity,1) transform = CATransform3DTranslate(transform,-abs(dist/1000)) attributes.transform3D = transform
或者您可以根据与中心的距离围绕y轴旋转项目,并为transform.m34提供一个小的负值,这样它就会有透视和更真实的外观.
替换此行:
attributes.transform3D = CATransform3DScale(CATransform3DIdentity,size: self.collectionView!.bounds.size) let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect) let currentAngle = dist / (CGRectGetWidth(visibleRect)/2) var transform = CATransform3DScale(CATransform3DIdentity,1) transform.m34 = -1.0 / 1000 transform = CATransform3DRotate(transform,-currentAngle,1,0) attributes.transform3D = transform