我正在制作一个集合视图来生成轮播效果。
我需要中心单元格与左侧和右侧的其他两个单元格重叠。中心单元需要始终位于顶部。
但是,当我尝试将侧面的细胞与中心的细胞重叠时,它不起作用。相反,右侧单元格(蓝色单元格)与中心单元格(黑色单元格)重叠,如下图所示。
用于此效果的代码如下:
以下是集合视图的视图控制器。
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
}
}
那么,我怎样才能使中心单元始终与其他两个单元格重叠?
答案 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
或者您可以根据与中心的距离围绕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