前言:
本篇文章不是分享collectionView的詳細(xì)使用教程, 而是屬于比較'高級'的collectionView使用技巧, 閱讀之前, 我想你已經(jīng)很熟悉collectionView的基本使用, 如果不是很熟悉, 建議在以后熟悉一下. 那么在本篇結(jié)束后, 你也能夠很輕松的使用collectionView來實(shí)現(xiàn), 當(dāng)下比較流行和比較炫酷的效果以及你想要自己實(shí)現(xiàn)的其他的效果.這里就實(shí)現(xiàn)三種比較常用的效果: 線性布局, 瀑布流布局, 圓形布局, 其他的各種自定義的布局你將是會有能力自己實(shí)現(xiàn)的.Demo地址
最終效果
一` 首先了解下UICollectionViewLayoutAttributes,
- 當(dāng)你看這些屬性的時候, 有沒有感覺到是一個view應(yīng)該有的屬性, 實(shí)際上, collectionVIew里面的所有的cell我們并沒有給它直接設(shè)置過他在collectionView上面的frame等屬性, 它的相關(guān)的設(shè)置都是由UICollectionViewLayoutAttributes來完成的.
- 每一個cell都對應(yīng)的有一個UICollectionViewLayoutAttributes來設(shè)置他的一些屬性, 當(dāng)我們在修改他對應(yīng)的UICollectionViewLayoutAttributes的相關(guān)的屬性的時候, 就間接的實(shí)現(xiàn)了對響應(yīng)的cell的相關(guān)屬性的修改.
- 所以我們對collectionViewCell的很多自定義就落在了對相應(yīng)的UICollectionViewLayoutAttributes上面
public var frame: CGRect
public var center: CGPoint
public var size: CGSize
// 用于實(shí)現(xiàn)一些3D效果
public var transform3D: CATransform3D
@available(iOS 7.0, *)
public var bounds: CGRect
@available(iOS 7.0, *)
// 更改transform可以方便的實(shí)現(xiàn)一些縮放, 旋轉(zhuǎn)效果
public var transform: CGAffineTransform
public var alpha: CGFloat
public var zIndex: Int // default is 0
// 初始化方法, 分別對應(yīng)為collectionView里面的幾種reusebleView
//cell
public convenience init(forCellWithIndexPath indexPath: NSIndexPath)
//SupplementaryView
public convenience init(forSupplementaryViewOfKind elementKind: String, withIndexPath indexPath: NSIndexPath)
// DecorationView
public convenience init(forDecorationViewOfKind decorationViewKind: String, withIndexPath indexPath: NSIndexPath)
二` 了解UICollectionViewFlowLayout
- 要完成collectionView的布局是需要設(shè)置他的屬性 collectionViewLayout, 當(dāng)使用storyboard的時候是默認(rèn)為UICollectionViewFlowLayout
- UICollectionViewFlowLayout是系統(tǒng)提供的一種網(wǎng)格布局, 通常情況下你只需要設(shè)置一下下面列舉的一些屬性, 就可以達(dá)到普通的collectionView的使用效果---網(wǎng)格效果, 同時你可以使用UICollectionViewDelegateFlowLayout 來實(shí)現(xiàn)一些比較簡單的動態(tài)更改cell的布局的效果
// 最小的行距
public var minimumLineSpacing: CGFloat
// 最小的列距
public var minimumInteritemSpacing: CGFloat
// cell的大小
public var itemSize: CGSize
// collectionView的滾動方向
public var scrollDirection: UICollectionViewScrollDirection // default is UICollectionViewScrollDirectionVertical
// headersize
public var headerReferenceSize: CGSize
public var footerReferenceSize: CGSize
public var sectionInset: UIEdgeInsets
```
####三` 強(qiáng)大的UICollectionViewLayout
* 要完成collectionView的布局是需要設(shè)置他的屬性 collectionViewLayout, 當(dāng)使用storyboard的時候是默認(rèn)為UICollectionViewFlowLayout, 實(shí)際上UICollectionViewFlowLayout是繼承自UICollectionViewLayout, 由系統(tǒng)實(shí)現(xiàn)的一種collectionView的布局
* 所以我們可以繼承UICollectionViewLayout來自定義我們想要的布局
* 實(shí)現(xiàn)自定義的布局并不是很復(fù)雜, 官方文檔中已經(jīng)說明了相關(guān)的方法, 這里直接分享給大家
下面是自定義UICollectionViewLayout時比較常用到的一些方法
1. collectionView每次需要重新布局(初始, layout 被設(shè)置為invalidated ...)的時候會首先調(diào)用這個方法prepareLayout()
所以Apple建議我們可以重寫這個方法來為自定義布局做一些準(zhǔn)備的操作,
在cell比較少的情況下, 我們一般都可以在這個方法里面計(jì)算好所有的cell布局
并且緩存下來, 在需要的時候直接取相應(yīng)的值即可, 以提高效率
func prepareLayout()
2. 然后會調(diào)用layoutAttributesForElementsInRect(rect: CGRect)方法獲取到rect范圍內(nèi)的cell的所有布局, 這個rect大家可以打印出來看下, 和collectionView的bounds不一樣, size可能比collectionView大一些, 這樣設(shè)計(jì)也許是為了緩沖
Apple要求這個方法必須重寫, 并且提供相應(yīng)rect范圍內(nèi)的cell的所有布局的
UICollectionViewLayoutAttributes, 如果之前我們已經(jīng)計(jì)算好了,
就可以直接返回就可以了, 當(dāng)然你可以比如只返回rect范圍內(nèi)的cell的布局,
而不是所有的cell的布局, 不過這樣的話你需要設(shè)置下一個方法
func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
3. 當(dāng)collectionView的bounds變化的時候會調(diào)用
shouldInvalidateLayoutForBoundsChange(newBounds: CGRect)這個方法
如果我們的布局是會時刻變化的, 需要在滾動的過程中重新布局 , 那么我們需要
設(shè)置這個方法的返回值為true, 默認(rèn)為false
- 當(dāng)返回值為true的時候會將collectionView的layout設(shè)置為invalidated,
將會使collectionView重新調(diào)用上面的prepareLayout()...方法重新獲得布局 - 同時, 當(dāng)屏幕旋轉(zhuǎn)的時候collectionView的bounds也會調(diào)用這個方法
如果設(shè)置為false, 那么將不會達(dá)到屏幕適配的效果, - 需要注意的是, 當(dāng)collectionView執(zhí)行一些操作(delete insert reload)等的時候,
不會調(diào)用這個方法, 會直接重新調(diào)用上面的prepareLayout()...方法重新獲得布局
public func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool
4. 需要設(shè)置collectionView 的滾動范圍 collectionViewContentSize()
自定義的時候, 必須重寫這個方法, 并且返回正確的滾動范圍, collectionView才能正常的滾動
public func collectionViewContentSize() -> CGSize
5. 以下方法, Apple建議我們也重寫, 返回正確的自定義對象的布局
因?yàn)橛袝r候當(dāng)collectionView執(zhí)行一些操作(delete insert reload)等系統(tǒng)會調(diào)用這些方法獲取布局, 如果沒有重寫, 可能發(fā)生意想不到的效果
自定義cell布局的時候重寫
public func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes?
自定義SupplementaryView的時候重寫
public func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes?
自定義DecorationView的時候重寫
public func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes?
6. 這個方法是當(dāng)collectionView將停止?jié)L動的時候調(diào)用, 我們可以重寫它來實(shí)現(xiàn), collectionView停在指定的位置(比如照片瀏覽的時候, 你可以通過這個實(shí)現(xiàn)居中顯示照片...)
public func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
... 同時里面還有很多的方法我們可以重寫來實(shí)現(xiàn)更多的效果, 但是這里, 就先介紹這么多的方法來實(shí)現(xiàn)自定義collectionView的布局
------------
###下面通過例子介紹下具體的使用
####圓形布局的實(shí)現(xiàn)
* 繼承自UICollectionViewLayout, 重寫prepareLayout(),在這里面我們計(jì)算好所有的cell布局
override func prepareLayout() {
// 一定要調(diào)用super
super.prepareLayout()
// 初始化需要的數(shù)據(jù)
// 總的cell
totalNum = collectionView!.numberOfItemsInSection(0)
// 每次計(jì)算前需要清零
layoutAttributes = []
// 圓心
center = CGPoint(x: Double(collectionView!.bounds.width * 0.5), y: Double(collectionView!.bounds.height * 0.5))
// 圓半徑
radius = min(collectionView!.bounds.width, collectionView!.bounds.height) / 3.0
var indexPath: NSIndexPath
for index in 0..<totalNum {
indexPath = NSIndexPath(forRow: index, inSection: 0)
// 在這個方法里面設(shè)置了每個indexPath對應(yīng)的cell的布局,
// 即實(shí)現(xiàn)我們想要的布局
let attributes = layoutAttributesForItemAtIndexPath(indexPath)!
layoutAttributes.append(attributes)
}
}
* 重寫方法設(shè)置每個cell的布局
// Apple建議要重寫這個方法, 因?yàn)槟承┣闆r下(delete insert...)系統(tǒng)可能需要調(diào)用這個方法來布局
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
// 設(shè)置cell的大小
attributes.size = CGSize(width: 60.0, height: 60.0)
// 當(dāng)前cell的角度
// 注意類型轉(zhuǎn)換
let angle = 2 * CGFloat(M_PI) * CGFloat(indexPath.row) / CGFloat(totalNum)
// 一點(diǎn)點(diǎn)數(shù)學(xué)轉(zhuǎn)換
attributes.center = CGPoint(x: center.x + radius*cos(angle), y: center.y + radius*sin(angle))
return attributes
}
數(shù)學(xué)計(jì)算過程(寫得不好看##<<>>##)
![丑陋的文筆.png](http://upload-images.jianshu.io/upload_images/1271831-1c7c180626674873.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* 重寫方法返回計(jì)算好的布局
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return layoutAttributes
}
* 重寫方法設(shè)置collectionView的滾動范圍(這里不滾動)
override func collectionViewContentSize() -> CGSize {
return collectionView!.bounds.size
}
----
這樣就實(shí)現(xiàn)了自定義的圓形布局, 還是比較簡單!!!!
-----
-----
>關(guān)于瀑布流的布局, 自定義過程和這個相似, 就不貼代碼了, [Demo地址](https://github.com/jasnig/CollectionViewLayout)里面有詳細(xì)的代碼注釋, 直接大家看代碼吧, 如果對你有幫助, 歡迎關(guān)注, 歡迎star
另外Demo里的布局大家是可以直接拿來使用的
以后你也有能力自己實(shí)現(xiàn)各種炫酷的布局效果了