CollectionView水平和豎直瀑布流的實現

  最近在項目中須要實現一個水平的瀑布流(即每一個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 有問題的地方還請大神拍磚指教。

相關文章
相關標籤/搜索