好久沒寫過文章了,趁現(xiàn)在得空寫一下最近實現(xiàn)的UICollectionViewFlowLayout實現(xiàn)的左右滑動功能译荞,先上效果圖:
注:這里的分頁方式是使用UIScrollView的setContentOffset方法來實現(xiàn)的,動畫效果上感覺原生的分頁好休弃,但是我不懂如何用pageEnable或其他更好的方法實現(xiàn)由三個cell顯示在屏幕的分頁(剛好能使一個在屏幕正中央)吞歼,如果有朋友有更好的實現(xiàn)方式或者有用過現(xiàn)成的框架,請記得告訴我一下塔猾,謝謝篙骡!
首先我在StoryBoard里面設(shè)置UICollectionView的約束,設(shè)置距離左邊和右邊都是0,高度隨著我這邊自定義的TopView和BottomView改變
設(shè)置UICollectionViewCell的Size
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let sizeWidth = WIDTH - 84
let sizeHeight = collectionView.bounds.size.height
return CGSizeMake(sizeWidth, sizeHeight)
}
這里我設(shè)置UICollectionViewCell的尺寸是距離左右分別是42糯俗,然后高度和UICollectionView一樣高
- 自定義的UICollectionViewFlowLayout
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.itemSize = CGSizeMake(itemWidth, itemHeight)
self.scrollDirection = .Horizontal
self.minimumLineSpacing = 6
}
首先設(shè)置UICollectionView的滾動方向以及Cell之間的最小間距尿褪,在這里我設(shè)置為6是因為視覺感官上左右兩邊的Cell不至于相差太遠,在這里我放兩張對比吧叶骨,不縮小和縮小過的圖:
繼續(xù)實現(xiàn)UICollectionViewFlowLayout里面的方法
// 布局
override func prepareLayout() {
// scrollRate
collectionView?.decelerationRate = UIScrollViewDecelerationRateNormal
collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: collectionView!.bounds.width / 2 - (WIDTH - 84) / 2, bottom: 0, right: collectionView!.bounds.width / 2 - (WIDTH - 84) / 2)
}
在這里設(shè)置了UICollectionView的contentInset茫多,設(shè)置了第一個cell和最后一個cell距離左邊和右邊距離,剛好讓第一個cell和最后一個cell位于最屏幕最中間
// 邊界改變就重新布局忽刽,默認是false
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
這個方法表示只要顯示的邊界發(fā)生改變就重新布局(默認是false)
// 返回所有的UICollectionViewLayoutAttributes
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let array = super.layoutAttributesForElementsInRect(rect)
// 可見矩陣
let visiableRect = CGRectMake(self.collectionView!.contentOffset.x, self.collectionView!.contentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)
for attributes in array! {
// 不在可見區(qū)域的attributes不變化
if !CGRectIntersectsRect(visiableRect, attributes.frame) {continue}
let frame = attributes.frame
let distance = abs(collectionView!.contentOffset.x + collectionView!.contentInset.left - frame.origin.x)
let scale = min(max(1 - distance/(collectionView!.bounds.width), 0.85), 1)
attributes.transform = CGAffineTransformMakeScale(scale, scale)
}
return array
}
這個方法首先取到所有的UICollectionViewLayoutAttributes然后對其進行放大縮小操作天揖。
visiableRect是當前屏幕的Rect,結(jié)合CGRectIntersectsRect方法來判斷哪些cell顯示在屏幕跪帝,好讓我們對其進行操作
distance其實就是計算左邊或者右邊的距離今膊,如果剛好在那個位置(就是cell在中心)的時候,cell就最大
最后就是transform變大變小了
如果這里有些不懂的話伞剑,可以去看看這篇文章:
How to Create an iOS Book Open Animation: Part 1
這里多說一個方法斑唬,如果想連續(xù)滑動幾個Cell的話,不設(shè)置分頁功能的話黎泣,可以再加這個方法恕刘,這個方法是滑動的時候始終讓一個Cell保持在屏幕正中央
// 當停止滑動,時刻有一張圖片是位于屏幕最中央的抒倚。
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let lastRect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height)
//獲得collectionVIew中央的X值(即顯示在屏幕中央的X)
let centerX = proposedContentOffset.x + self.collectionView!.frame.width * 0.5;
//這個范圍內(nèi)所有的屬性
let array = self.layoutAttributesForElementsInRect(lastRect)
//需要移動的距離
var adjustOffsetX = CGFloat(MAXFLOAT);
for attri in array! {
if abs(attri.center.x - centerX) < abs(adjustOffsetX) {
adjustOffsetX = attri.center.x - centerX;
}
}
return CGPointMake((proposedContentOffset.x + adjustOffsetX), proposedContentOffset.y)
}
- 說完了UICollectionViewFlowLayout褐着,我們要簡單模仿分頁的功能(滑動效果感官上不如系統(tǒng)的分頁)
func scrollViewDidScroll(scrollView: UIScrollView) {
if self.lastContentOffset < scrollView.contentOffset.x {
self.scrollToRight = true
}
else{
self.scrollToRight = false
}
self.lastContentOffset = scrollView.contentOffset.x
}
這里主要是判斷向哪邊移動
private var scrollDistance: CGFloat = WIDTH - 84 + 6
func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
if self.scrollToRight {
// 向右移動
let currentCount = Int(scrollView.contentOffset.x/self.scrollDistance) + 1
if currentCount == 1 {
let totalScrollDistance = self.scrollDistance - self.collection.contentInset.left
scrollView.setContentOffset(CGPointMake(CGFloat(currentCount)*totalScrollDistance, 0), animated: true)
}
else if currentCount < self.vms.count && currentCount > 1 {
let totalScrollDistance = CGFloat(currentCount)*self.scrollDistance - self.collection.contentInset.left
scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
}
else{
let totalScrollDistance = CGFloat(self.vms.count - 1)*self.scrollDistance - self.collection.contentInset.left
scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
}
}
else {
// 向左移動
let currentCount = Int(scrollView.contentOffset.x/self.scrollDistance)
let totalScrollDistance = CGFloat(currentCount)*self.scrollDistance - self.collection.contentInset.left
scrollView.setContentOffset(CGPointMake(totalScrollDistance, 0), animated: true)
}
}
scrollDistance:代表一個Cell的width和Cell間距的和
這里有一個值注意的點:
移動第一個cell的時候,只需要移動scrollDistance減去contentInset.left的距離托呕,因為contentInset.left的距離不計算在scrollView.contentOffset.x里面
移動第一個之外的cell的話含蓉,就是要移動一個scrollDistance的距離了
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
let cell = self.collection.cellForItemAtIndexPath(NSIndexPath.init(forItem: self.currentRow, inSection: 0)) as? XXCell
cell?.timer?.invalidate()
// compute currentRow
let distanceWithoutFirstRow = scrollView.contentOffset.x - (self.scrollDistance - self.collection.contentInset.left)
if distanceWithoutFirstRow < 0 {
self.currentRow = 0
}
else{
self.currentRow = Int(distanceWithoutFirstRow/self.scrollDistance) + 1
}
self.loadBottomView(self.currentRow)
}
這個是滑動結(jié)束之后調(diào)用的方法,好像是使用了scrollView.setContentOffset才會調(diào)用這個方法
這里我們要計算當前顯示在中央的是哪一個Cell项郊,然后再更新與之對應(yīng)的BottomView的內(nèi)容馅扣。
實現(xiàn)方式差不多就是這樣了,寫個文章記錄一下着降!