纵向瀑布流

相信所有人都应该知道瀑布流是用集合视图UICollectionView实现的,这里呢我们先回忆下集合视图最常用的几个方法

/**
* 设置某个Item是否可以移动
*/
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath ;

/**
*移动item的时候调用的方法
*/
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath ;

//拼音索引列表
- (nullable NSArray *)indexTitlesForCollectionView:(UICollectionView *)collectionView ;

//索引路径
- (NSIndexPath *)collectionView:(UICollectionView *)collectionView indexPathForIndexTitle:(NSString *)title atIndex:(NSInteger)index ;

//cell的高亮效果显示
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
 - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
 - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;

//cell的选中状态
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
 - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

//支持复制粘贴操作
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
 - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;
 - (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

言归正传,UICollectionView的精髓就是UICollectionViewLayout,这也是UICollectionView和UITableView最大的不同。UICollectionViewLayout决定了UICollectionView是如何显示在界面上的。在展示之间,一般需要生成合适的UICollectionViewLayout的子类对象,并将其赋值到UICollectionView的布局属性上。
UICollectionViewFlowLayout是UICollectionViewLayout的子类。这个布局是最简单最常用的。它实现了直线对其的布局排布方式,Gird View就是用UICollectionViewFlowLayout布局方式。

UICollectionViewLayout布局的具体思路:

  • 设置itemSzie属性,它定义了每一个item的大小。在一个示例中通过设置layout的itemSize属性全局的设置了cell的尺寸。如果想要对某个cell定制尺寸,可以使用- (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath方法实现

  • 设置间隔
    间隔可以指定item之间的间隔和每一行之间的间隔。间隔和itemSzie一样,既有全局属性,也可以对每一个item设定:

@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) CGFloat minimumInteritemSpacing;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
  • 设置Header和Footer的尺寸
    设置Header和Footer的尺寸也分为全局和局部。在这里需要注意滚动的方向,滚动方向不同,header和footer的宽度和高度只有一个会起作用。垂直滚动时section间宽度为尺寸的高。
@property (nonatomic) CGSize headerReferenceSize;
@property (nonatomic) CGSize footerReferenceSize;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
  • 设置内边距
@property (nonatomic) UIEdgeInsets sectionInset;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

说了这么多,那么实现瀑布流的具体步骤究竟是怎样的呢?

1、进行初始化

  • 调用- (void)prepareLayout

2、设置collectionView的可显示范围

  • 重载- (CGSize)collectionViewContentSize

3、设置cell的布局属性

  • 重载- (NSArray *)layoutAttributesForElementsInRect:(CGRect)

4、设置对应的indexPath的位置的cell的布局属性,如果没有就不用重载

  • 重载- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

5、设置对应indexPath的位置的追加视图的布局属性,如果没有就不用重载

  • 重载- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

6、设置对应indexPath的位置的装饰视图的布局属性,如果没有也不需要重载

  • 重载- (nullable UICollectionViewLayoutAttributes )layoutAttributesForDecorationViewOfKind:(NSString)elementKind atIndexPath:(NSIndexPath *)indexPath

7、当collectionView的frame有新改变(发生移动)时是否应该刷新,其若返回YES则重新布局

  • 重载- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

8、如果想要对某个cell定制尺寸,可以使用,如果没有也不需要重载

  • 重载 - (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
这里说明下,最重要的是前三个方法,剩下的方法看实际情况选择是否进行重载
#import "WaterfallFlowLayout.h"

@interface WaterfallFlowLayout (){
    int _maxHeight; //记录最高列高度
}
//视图的属性(UICollectionViewLayoutAttributes)数组
@property (nonatomic, strong)NSMutableArray *attributesArray;

@end

@implementation WaterfallFlowLayout

//lazy
- (NSMutableArray *)attributesArray{
    if (_attributesArray == nil) {
        _attributesArray = [[NSMutableArray alloc] init];
    }
    return _attributesArray;
}

//1.复写 prepareLayout 方法,准备工作,此时的 collectionView 就像一个空的 scrollView 所有的 item 都需要我们自己手动的确定,他的 frame 就像一个画板,我们需要把视图都画在这个画板上。
//初始化           调用顺序 ···· 1
- (void)prepareLayout{
    
    //item的最小列间距         (minimumLineSpacing  行间距)
    CGFloat space = self.minimumInteritemSpacing;
    CGFloat contentSize = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right;
    //每个单元格的宽度
    CGFloat itemWidth = (contentSize - (space * (self.lineNum-1)))/self.lineNum;
    //根据宽度计算每个单元格的属性
    [self computeAttributesWithItemWidth:itemWidth];
}

//计算每个单元格的 attributes 方法,我们保存每一列的总高度,在创建每个单元格的 attributes 时挑选最短的那一列,把当前单元格添加到这个最短列
- (void)computeAttributesWithItemWidth:(CGFloat)width{
    
    //存储每列个数和每列总高度的数组
    CGFloat columnHeight[self.lineNum];
    CGFloat columnNum[self.lineNum];
    //初始化数组
    for (int i = 0; i < self.lineNum; i ++) {
        columnNum[i] = 0;
        //sectionInset  UIEdgeInsets类型  边距
        columnHeight[i] = self.sectionInset.top;
    }
    
    //循环创建每个单元格的 attributes 属性
    for (int i = 0; i < self.dataList.count; i ++) {
        //找到最短列
        int index = [self computerMinHeightWithArray:columnHeight];
        //创建位置并获取indexPath位置上cell对应的布局属性
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        //计算每个item的位置,大小
        CGFloat itemX = index * (width+self.minimumLineSpacing) + self.sectionInset.left;
        CGFloat itemY = columnHeight[index];
        CGFloat itemHeight = [self makeNum];
        
        attributes.frame = CGRectMake(itemX, itemY, width, itemHeight);
        
        [self.attributesArray addObject:attributes];
        
        columnHeight[index] += itemHeight+self.minimumInteritemSpacing;
        columnNum[index] ++;
    }
    
    //计算最高列
    int maxIndex = [self computerMaxHeightWithArray:columnHeight];
    _maxHeight = columnHeight[maxIndex];
    
    CGFloat aveHeight = (columnHeight[maxIndex] - self.sectionInset.top - columnNum[maxIndex]*self.minimumLineSpacing)/columnNum[maxIndex];
    self.itemSize = CGSizeMake(width, aveHeight);
    
}

//生成随机数
- (CGFloat)makeNum{
    
    CGFloat height = arc4random_uniform(255);
    if (height >= 80) {
        return height;
    }else{
        return [self makeNum];
    }
}

//寻找最短列   返回最短列
- (int)computerMinHeightWithArray:(CGFloat *)array{
    
    int minIndex = 0;
    CGFloat min = CGFLOAT_MAX;
    for (int i = 0; i < self.lineNum; i ++) {
        if (min > array[i]) {
            minIndex = i;
            min = array[i];
        }
    }
    
    return minIndex;
}

//寻找最长列
- (int)computerMaxHeightWithArray:(CGFloat *)array{
    
    int maxIndex = 0;
    CGFloat max = 0;
    for (int i = 0; i < self.lineNum; i ++) {
        if (max < array[i]) {
            maxIndex = i;
            max = array[i];
        }
    }
    
    return maxIndex;
}

//写完上一步,这个垂直瀑布流的布局类基本写完了,但是我们发现内容尺寸还是不太对,之前这个内容尺寸是布局类根据自身的属性 itemSize 来计算的,而 itemSize 取的是平均值,这样算出的内容尺寸会有些偏差,我们再重写一下 collectionViewContentSize 方法
//内容尺寸大小            调用顺序 ···· 2
- (CGSize)collectionViewContentSize{
    return CGSizeMake(self.collectionView.bounds.size.width, _maxHeight);
}

//返回cell的布局属性                   调用顺序 ···· 3
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    return self.attributesArray;
}
@end
纵向瀑布流_第1张图片
效果图

你可能感兴趣的:(纵向瀑布流)