自定义UICollectinviewFlowLayout,即实现瀑布流

IsaMinnick 9年前

来自: http://www.cnblogs.com/xueyao/p/5188321.html

如图所示,通过实现不规则的网格分布,来显示出不同的效果。因为集合视图必须要指定布局还可以显示,所以自定义布局就可以实现瀑布流的效果。

//创建布局对象      WaterFlowLayout *flowLayout = [[WaterFlowLayout alloc] init];            flowLayout.delegate = self;      flowLayout.numberOfColumn = 3;            //创建集合视图      UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:flowLayout];

因为系统自带的布局有四个方法,分别实现了item大小,分区间隔,最小行间距,item之间的间隙大小

@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>  @optional  - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;  - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;

所以,自定义FlowLayout,并定义协议,以便定义这些方法。

@protocol WaterFlowLayoutDelegate <NSObject>    //关键方法,此方法的作用是返回每一个item的size大小  //数据中原始图片大小  - (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;  //分区间隔  - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;  //得到 item之间的间隙大小  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;  //最小行间距  - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;    @end    @interface WaterFlowLayout : UICollectionViewLayout    //瀑布流一共多少列  @property (nonatomic, assign) NSUInteger numberOfColumn;    @property (nonatomic, assign) id<WaterFlowLayoutDelegate>delegate;

看图可知,最小高的的item,将作为下一列item的起点。

@interface WaterFlowLayout ()    //存放每一列的高度  @property (nonatomic, retain) NSMutableArray *columnHeightsArray;    //存放 每一个item的 属性 包含 frame以及下标  @property (nonatomic, retain) NSMutableArray *attributesArray;    @end    @implementation WaterFlowLayout    //获取最小高度的方法  - (CGFloat)minHeight  {      CGFloat min = 100000;      for (NSNumber *height in _columnHeightsArray) {          CGFloat h = [height floatValue];          if (min > h) {              min = h;          }      }      return min;  }    //获取最大值  - (CGFloat)maxHeight  {      CGFloat max = 0;      for (NSNumber *height in _columnHeightsArray) {          CGFloat h = [height floatValue];          if (max < h) {              max = h;          }      }      return max;  }    //找到最小高的下标  - (NSUInteger)indexOfMinHeight  {      NSUInteger index = 0;      for (int i = 0; i < [_columnHeightsArray count]; i ++) {          CGFloat height = [_columnHeightsArray[i] floatValue];          if (height == [self minHeight]) {              index = i;              return index;          }      }      return index;  }    //重写父类的布局方法  - (void)prepareLayout  {      [super prepareLayout];            _attributesArray = [[NSMutableArray alloc] init];            _columnHeightsArray = [[NSMutableArray alloc] initWithCapacity:self.numberOfColumn];            //给列高数组里面的对象赋初值      for (int i = 0; i < self.numberOfColumn; i ++) {          [_columnHeightsArray addObject:@0.0];      }            CGFloat totalWidth = self.collectionView.frame.size.width;            //创建 每个item frame中的x、y      CGFloat x = 0;      CGFloat y = 0;            NSUInteger itemCount = [self.collectionView numberOfItemsInSection:0];            for (int i = 0; i < itemCount; i ++) {          //得到集合视图中 列间隙的个数          NSUInteger numberOfSpace = self.numberOfColumn - 1;                    //代理对象执行代理方法,得到 item之间的间隙大小          CGFloat spaceWidth = [_delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];                    //求每列的宽度,也就是每个item的width          CGFloat width = (totalWidth - spaceWidth * numberOfSpace) / self.numberOfColumn;                              //获取每一个itemSize的大小          NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];                    //数据中原始图片大小          CGSize imageSize = [_delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];                //通过 约分公式得到固定宽之后的高度是多少          CGFloat height = width * imageSize.height / imageSize.width;                              UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];                    //记录每一个item的大小和位置          attribute.frame = CGRectMake(x, y, width, height);                    //数组保存每个item的位置信息          [_attributesArray addObject:attribute];                    NSLog(@"item = %d",i);          NSLog(@"x = %.2f y = %.2f width = %.2f height = %.2f",x,y,width,height);                    //求列高最小的那一列的下标          NSUInteger minHeightIndex = [self indexOfMinHeight];                    //求出最小列的高度          CGFloat minHeight = [_columnHeightsArray[minHeightIndex] floatValue];                    //求出行高          CGFloat lineHeight = [_delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];                    //上一次总的列高 加上 行高 加上新加上的item的height,才是现在这一列的总高度          //minHeight为最小列现在的高度          //lineHeight为行间距          //height为新加的item的高          _columnHeightsArray[minHeightIndex] = [NSNumber numberWithFloat:minHeight + lineHeight + height];                    //重新算最小列高的下标          minHeightIndex = [self indexOfMinHeight];                    //算下一次新加的item的x和y值          x = (spaceWidth + width) * minHeightIndex;                    y = [self minHeight];      }  }    //重写这个方法,可以返回集合视图的总高度  - (CGSize)collectionViewContentSize  {      return CGSizeMake(self.collectionView.frame.size.width, [self maxHeight]);  }      - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect  {      return _attributesArray;  }

注意,最后一个方法的实现,即- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect,如果这个方法不写,集合视图是显示不出来的,这个方法是次保存的每个item的信息重新告诉集合视图,进行显示。