前言
在寫這篇文章之前, 先祝賀自己,屬於我的GitHub終於來了。這也是我的GitHub的第一份代碼,以下文章的代碼均可以在Demo clone或下載。歡迎大家給予意見,覺得寫得不錯的也請不要吝惜你們的star。
瀑布流
先普及下什麼叫瀑布流
瀑布流,又稱瀑布流式布局。是比較流行的一種網站頁面布局,視覺表現為參差不齊的多欄布局,隨著頁面滾動條向下滾動,這種布局還會不斷加載數據塊並附加至當前尾部。最早采用此布局的網站是Pinterest,逐漸在國內流行開來。
UICollectionView
我們知道, UICollectionView是蘋果推出的繼UITableView的又一個非常十分及其牛逼的UI控件, 除了有UITableView的緩存池, 重用機制外, 更能由你自主打造item(tableView中稱cell, collectionView中稱item)的顯示布局, 只要你給它傳一個layout布局屬性, 他就能按照你的意願去顯示item。
UICollectionViewLayout
這裡我就用自定義類JRWaterFallLayout繼承UICollectionViewLayout來寫瀑布流布局.
需要手動實現的4個方法
注意:因為layoutAttributesForElementsInRect方法調用十分頻繁, 所以布局屬性的數組應該只計算一次保存起來而不是每次調用該方法的時候重新計算
代理
提供接口給外界修改一些瀑布流布局的參數, 例如顯示的列數, 列距, 行距, 邊緣距(UIEdgeInsets), 假如代理不實現該方法, 則用默認的參數.. 最最重要的還是item的高度!! 因為每個圖片(item)的高度是由圖片的寬高比和itemWidth來共同決定的. 所以itemHeight必須由代理來決定.這裡展示幾個代理方法 :
注意:由於上面所說的, layoutAttributesForElementsInRect方法調用十分頻繁, 所以代理方法勢必也會頻繁調用。但是並不是所有代理方法都是@required的, 所以在調用@optional的代理方法時需要如下代碼那樣每次都判斷代理是否響應了該選擇子,以防代理沒有實現該方法, 調用導致程序崩潰
if ( [self.delegate respondsToSelector:@selector(columnCountOfWaterFallLayout:)] ) { _columnCount = [self.delegate columnCountOfWaterFallLayout:self]; } else { _columnCount = JRDefaultColumnCount; }
每次都這樣判斷顯然效率很低, 我們可以在prepareLayout方法中進行一次性判斷,然後用一個flags結構體存儲起來,那麼下次我們在調用的時候直接對flag進行判斷即可。如下:
struct { // 記錄代理是否響應選擇子 BOOL didRespondColumnCount : 1; // 這裡的1是用1個字節存儲 BOOL didRespondColumnMargin : 1; BOOL didRespondRowMargin : 1; BOOL didRespondEdgeInsets : 1; } _delegateFlags; - (void)setupDelegateFlags { _delegateFlags.didRespondColumnCount = [self.delegate respondsToSelector:@selector(columnCountOfWaterFallLayout:)]; _delegateFlags.didRespondColumnMargin = [self.delegate respondsToSelector:@selector(columnMarginOfWaterFallLayout:)]; _delegateFlags.didRespondRowMargin = [self.delegate respondsToSelector:@selector(rowMarginOfWaterFallLayout:)]; _delegateFlags.didRespondEdgeInsets = [self.delegate respondsToSelector:@selector(edgeInsetsOfWaterFallLayout:)]; } // 那麼下次調用方法的時候就變成下面那麼優雅了 _columnCount = _delegateFlags.didRespondColumnCount ? [self.delegate columnCountOfWaterFallLayout:self] : JRDefaultColumnCount;
整個瀑布流layout最重要的是找到item擺放的位置. 正是layoutAttributesForItemAtIndexPath方法要做的是. 下面開始說說找這個item位置的思路
瀑布流layout思路
這裡本人一共需要用到2個可變數組和一個assign屬性, 一個用來記錄每列的高度,一個用來記錄所有itemAttributes. assign用來記錄高度最大的列的高度
/** itemAttributes數組 */ @property (nonatomic, strong) NSMutableArray *attrsArray; /** 每列的高度數組 */ @property (nonatomic, strong) NSMutableArray *columnHeights; /** 最大Y值 */ @property (nonatomic, assign) CGFloat maxY;
而在prepareLayout方法中, 以上2個數組都是要清空的,因為網絡請求的新數據到了,collectionView要重新布局的時候如果不清空,繼續往裡邊加東西的話,會導致item的布局就全部亂套了..
接下裡要處理的就是layoutAttributesForItemAtIndexPath方法中每個item該怎麼布局了,思路很簡單
創建一個UICollectionViewLayoutAttributes對象
根據collectionView的width及行間距等幾個參數,計算出item的寬度
找到最短列的列號
根據列號計算item的x值、y值, 詢問代理拿到item的高度
設置UICollectionViewLayoutAttributes對象的frame屬性
返回UICollectionViewLayoutAttributes對象
問題主要出在怎麼計算出x, y, width, height上,看圖說話。
詳細的計算步驟可以看Demo。
最後
理論上只要你有足夠強大的算法計算能力, 什麼顯示布局都能寫出來. collectionViewLayout並不止於瀑布流!
本文作者: 伯樂在線 - Jerry4me