最近在項目中須要實現一個水平的瀑布流(即每一個Cell的高度是固定的,可是長度是不固定的),由於須要重寫系統git
UICollectionViewLayout中的一些方法經過計算去實現手動佈局,因此本着代碼可複用的原則(其實就是懶,不想再寫一遍而已~~),乾脆把水平和豎直模式都集成到一個文件中,經過protocol去控制瀑布流的顯示模式。github
首先咱們須要瞭解一下UICollectionViewLayout是個啥玩意,爲何經過它能夠進行瀑布流的實現呢?在蘋果官方Api Reference中咱們能夠看到,UICollectionViewLayout的做用就是生成集合視圖的佈局信息。UICollectionViewFlowLayout對UICollectionViewLayout進行了擴充,UICollectionViewFlowLayout不但能夠重寫頁面的佈局,並且它還有一些屬性能夠用於方便的設置諸如cell之間的間距,cell和邊界的間距的值,所以咱們經過實現一個類繼承自UICollectionViewFlowLayout不但能夠重寫UICollectionViewLayout中的佈局方法,還能夠快捷的設置cell的間距。接下來咱們直接上乾貨。數組
在開始以前咱們經過Category來添加一些擴展的方法,以便於咱們獲取UICollectionView的佈局信息。佈局
typedef NS_ENUM(NSInteger, CGYFlowLayoutType)ui
{spa
CGYFlowLayoutTypeHorizontal = 1, //水平顯示模式,UICollectionView中的元素寬度不一樣,高度相同code
CGYFlowLayoutTypeVertical, //豎直現實模式,UICOllectionView中的元素寬度相同,高度不一樣orm
};blog
@protocol CGYFlowLayoutdataSource <NSObject>繼承
@required
/**
指定UICollectionView的顯示模式
@return CGYFlowLayoutType 瀑布流的展示方式
*/
- (CGYFlowLayoutType)CGYCollectionViewFlowLayoutType;
/**
水平模式下數組中傳入的是每一個元素的寬度,豎直模式下傳入元素的高度
@return 包含Cell高度或寬度的數組
*/
- (NSArray *)CGYFlowLayoutElementsSize;
/**
UICollectionView的寬度
@return UICollectioncView的寬度
*/
- (CGFloat)CGYFlowLayoutWidth;
@optional
/**
Cell高度不定時須要實現此方法,制定CollectionView中顯示的列數
@return UICollectionView的列數
*/
- (NSInteger)CGYFlowLayoutVerticalNumber;
/**
Cell寬度不定時須要實現此方法,元素的自定義高度
@return UICollectionView中每一個元素的高度
*/
- (CGFloat)CGYFlowLayoutHorizontalCommonHeight;
@end
@interface CGYFlowLayout () { NSArray *elementsSize; //佈局元素尺寸存儲數組 NSInteger verticalNumber; //豎直不規則佈局列數 NSMutableArray *_arrayPosition; //元素佈局位置存儲數組 NSMutableDictionary *_dicVerticalHeight; //對應列高度存儲字典 CGFloat flowLayoutWidth; //UICollectionView的寬度 CGFloat horizontalElementsHeight; //水平模式下每行元素的高度 CGFloat HorizontalHeight; //UICollectionView的總高度 CGYFlowLayoutType flowLayoutType; //佈局模式 }
在聲明一些屬性用於存儲計算相關的數據,準備工做完成之後就能夠開始咱們的佈局實現了。
首先,UICollectionViewLayout中有一個prepareLayout方法,這個方法在第一次須要佈局時馬上被調用,以後在每次佈局失效以前被調用,在這個方法里正好適合咱們寫計算佈局信息的邏輯代碼。
1 - (void)prepareLayout 2 { 3 [super prepareLayout]; 4 5 [self collectionViewFlowLayoutSource]; 6 7 switch (flowLayoutType) { //根據佈局方式選擇進行的方法 8 case CGYFlowLayoutTypeHorizontal: 9 { 10 [self flowLayoutHorizontal]; 11 } 12 break; 13 case CGYFlowLayoutTypeVertical: 14 15 [self flowLayoutVertical]; 16 } 17 default: 18 break; 19 } 20 }
Cell元素高度相同,寬度不一樣佈局計算方法:
#pragma mark - CGYFlowLayout 佈局方法實現
//水平不規則佈局方式實現
- (void)flowLayoutHorizontal
{
[_arrayPosition removeAllObjects];
CGFloat positionX = self.sectionInset.left;
HorizontalHeight = self.sectionInset.top;
CGFloat elementsWidth;
for (NSInteger i = 0 ; i < [elementsSize count] ; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
elementsWidth = [[elementsSize objectAtIndex:i] floatValue];
NSAssert(elementsWidth>flowLayoutWidth, @"這個元素比CollectionView的寬度還寬");
if ((positionX + self.sectionInset.right + elementsWidth) > flowLayoutWidth)
{
positionX = self.sectionInset.left;
HorizontalHeight = HorizontalHeight + horizontalElementsHeight + self.minimumInteritemSpacing;
attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);
positionX = positionX + elementsWidth + self.minimumLineSpacing;
}
else
{
attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);
positionX = positionX + elementsWidth + self.minimumLineSpacing;
}
[_arrayPosition addObject:attr];
}
HorizontalHeight = HorizontalHeight + horizontalElementsHeight;
}
在Cell元素高度固定時,代碼實現較爲簡單,只須要判斷當前行的寬度是否可以容下下一個Cell元素插入就好了,若是下一個元素插入後行寬超過了UICollectionView的寬度,就將Cell插入到下一行,最後計算出佈局的總高度信息。在每次插入Cell元素後,經過_arrayPosition數組記錄下Cell元素的位置信息。
Cell元素寬度相同,高度不相同時計算相對來講複雜一些,咱們須要判斷找出每次插入後最短的那一列,將下一個元素插入到最短的列中。咱們經過一個NSMutableDictionary來存儲每次插入後的列高,NSMutableDictionary的key值就是第幾列,value就是該列的高度,在每次插入後都刷新該列的高度值。
//豎直不規則佈局方式實現 - (void)flowLayoutVertical { [_arrayPosition removeAllObjects]; //從新佈局時移除全部 CGFloat positionX; //元素佈局X座標 CGFloat positionY; //元素佈局Y座標 CGFloat elementsHeight; //元素高度 NSInteger minHeightPosition; //最小高度行位置 _dicVerticalHeight = [[NSMutableDictionary alloc] init]; for(NSInteger i = 0 ; i < verticalNumber ; i++) { //初始化高度字典 [_dicVerticalHeight setValue:@"0" forKey:[NSString stringWithFormat:@"%ld",(long)i]]; } CGFloat elementsWidth = (flowLayoutWidth - self.minimumLineSpacing*(verticalNumber - 1)-self.sectionInset.left - self.sectionInset.right + 1)/verticalNumber; for (NSInteger i = 0 ; i < [elementsSize count] ; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; minHeightPosition = [self MinHeightPosition]; positionX = self.sectionInset.left + (elementsWidth + self.minimumLineSpacing)*minHeightPosition; positionY =self.sectionInset.top + [[_dicVerticalHeight valueForKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]] floatValue]; elementsHeight = [[elementsSize objectAtIndex:i] floatValue]; attr.frame = CGRectMake(positionX, positionY, elementsWidth, elementsHeight); [_dicVerticalHeight setValue:[NSNumber numberWithFloat:(positionY + elementsHeight + self.minimumInteritemSpacing)] forKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]]; [_arrayPosition addObject:attr]; } }
當咱們在prepareLayout方法中計算出整個UIcollectionView中全部cell的position後,咱們能夠經過選擇重寫layoutAttributesForElementsInRect:(CGRect)rect方法返回一個包含全部cell佈局信息的數組。
//返回計算好的佈局數組信息 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { return _arrayPosition; }
如今咱們拿到了全部cell的佈局信息能夠完事了嗎?很抱歉,答案是NO!!!UICollectionViewLayout強硬的規定了,當你重寫了它的時候,你就必需要實現一個叫作collectionViewContentSize的方法,這個方法肯定了UICollectionView可滑動的範圍,我試了一下,果真不重寫它咱們的CollectionView是動不了的。。。
- (CGSize)collectionViewContentSize { if (flowLayoutType == CGYFlowLayoutTypeVertical) { return CGSizeMake(flowLayoutWidth, [self maxHeightPosition]); } else { return CGSizeMake(flowLayoutWidth, HorizontalHeight); } }
當UICollectionView的元素高度固定時,返回值就是咱們記錄下來的HorizontalHeight,可是當元素高度不固定時,咱們就要從全部列中找出高度最高的那一列,並返回那一列的高度信息,這樣才能夠保證在滑動時每一個Cell都是可見的。
代碼的github地址是:https://github.com/cgy-tiaopi/CGYUICollectionViewFlowLayout 有問題的地方還請大神拍磚指教。