詳細(xì)分享UICollectionView的自定義布局(瀑布流, 線性, 圓形...)

前言:

本篇文章不是分享collectionView的詳細(xì)使用教程, 而是屬于比較'高級'的collectionView使用技巧, 閱讀之前, 我想你已經(jīng)很熟悉collectionView的基本使用, 如果不是很熟悉, 建議在以后熟悉一下. 那么在本篇結(jié)束后, 你也能夠很輕松的使用collectionView來實(shí)現(xiàn), 當(dāng)下比較流行和比較炫酷的效果以及你想要自己實(shí)現(xiàn)的其他的效果.這里就實(shí)現(xiàn)三種比較常用的效果: 線性布局, 瀑布流布局, 圓形布局, 其他的各種自定義的布局你將是會有能力自己實(shí)現(xiàn)的.Demo地址

最終效果

line.gif
water.gif
circle.gif

一` 首先了解下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)各種炫酷的布局效果了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骚揍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啰挪,更是在濱河造成了極大的恐慌信不,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脐供,死亡現(xiàn)場離奇詭異浑塞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)政己,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門酌壕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歇由,你說我怎么就攤上這事卵牍。” “怎么了沦泌?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵糊昙,是天一觀的道長。 經(jīng)常有香客問我谢谦,道長释牺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任回挽,我火速辦了婚禮没咙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘千劈。我一直安慰自己祭刚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涡驮,像睡著了一般暗甥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捉捅,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天撤防,我揣著相機(jī)與錄音,去河邊找鬼棒口。 笑死即碗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的陌凳。 我是一名探鬼主播剥懒,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼合敦!你這毒婦竟也來了初橘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤充岛,失蹤者是張志新(化名)和其女友劉穎保檐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崔梗,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夜只,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒜魄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扔亥。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谈为,靈堂內(nèi)的尸體忽然破棺而出旅挤,到底是詐尸還是另有隱情,我是刑警寧澤伞鲫,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布粘茄,位于F島的核電站,受9級特大地震影響秕脓,放射性物質(zhì)發(fā)生泄漏柒瓣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一吠架、第九天 我趴在偏房一處隱蔽的房頂上張望芙贫。 院中可真熱鬧,春花似錦诵肛、人聲如沸屹培。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褪秀。三九已至,卻和暖如春薛训,著一層夾襖步出監(jiān)牢的瞬間媒吗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工乙埃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闸英,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓介袜,卻偏偏與公主長得像甫何,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遇伞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容