UICollectionView的布局主要是通过UICollectionViewLayout的子类来管理的,所以我们可以重写UICollectionViewLayout来实现对UICollectionView布局样式的修改。
实现原理
其实实现的本质就是重新计算UICollectionVIew每个item的frame和UICollectionVIew的contentSize。
-
核心代码
// 必须实现的父类方法 - (void)prepareLayout { [super prepareLayout]; if (self.itemsCount != [self.collectionView numberOfItemsInSection:0]) { self.itemsCount = [self.collectionView numberOfItemsInSection:0]; self.itemWidth = (ScreenWidth - (self.interval * (self.columnNumbers + 1))) / self.columnNumbers; } } // 设置UICollectionView的内容大小,道理与UIScrollView的contentSize类似 - (CGSize)collectionViewContentSize { CGFloat contentHeight = 0; for (NSNumber *number in self.columnsHeight) { contentHeight = MAX(contentHeight, number.floatValue); }; return CGSizeMake(ScreenWidth, contentHeight); } // 初始话Layout外观,返回所有的布局属性 // 这个方法在滑动的时候会调用 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { // TODO:// 这儿可以做缓存来提高效率 // 每次清空事前的item的height,重新计算 [self.columnsHeight removeAllObjects]; for (int i = 0; i < _columnNumbers; i ++) { [self.columnsHeight addObject:[NSNumber numberWithFloat:0]]; } NSMutableArray *attributes = [[NSMutableArray alloc] init]; for (int i = 0; i < self.itemsCount; i ++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } return attributes; } // 根据不同的indexPath,给出布局 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat itemHeight = [self.delegate collectionView:self.collectionView layout:self heightForItemAtIndexPath:indexPath]; // 筛选UI上显示的最短的那一列 __block CGFloat minHeight = [self.columnsHeight firstObject].floatValue; __block NSUInteger minIndex = 0; [self.columnsHeight enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { minHeight = MIN(minHeight, obj.floatValue); if (minHeight == obj.floatValue) { minIndex = idx; } }]; UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];; CGFloat x = minIndex * (self.interval + self.itemWidth) + self.interval; CGFloat y = minHeight + self.interval; layoutAttributes.frame = CGRectMake(x, y, self.itemWidth, itemHeight); self.columnsHeight[minIndex] = [NSNumber numberWithFloat:self.columnsHeight[minIndex].floatValue + itemHeight + self.interval]; return layoutAttributes; }
-
涉及到的属性和代理(.h文件)
@protocol ZMCollectionViewFlowLayoutDelegate; @interface ZMCollectionViewFlowLayout : UICollectionViewLayout /** 瀑布流显示的列数,默认显示两列 */ @property (nonatomic, assign) NSUInteger columnNumbers; /** 每列之间的间隔距离,默认10 */ @property (nonatomic, assign) CGFloat interval; @property (nonatomic, weak) id<ZMCollectionViewFlowLayoutDelegate> delegate; @end @protocol ZMCollectionViewFlowLayoutDelegate <NSObject> // 获取item的高度,应为默认item的宽度可以通过间隔和列数计算出来的,所以这儿只需要高度即可 - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout heightForItemAtIndexPath:(NSIndexPath *)indexPath; @end
-
.m文件
@interface ZMCollectionViewFlowLayout()@property (nonatomic, assign) NSUInteger itemsCount; @property (nonatomic, assign) CGFloat itemWidth; @property (nonatomic, strong) NSMutableArray <NSNumber *> *columnsHeight; // 保存当前每一列的高度 @end
-
使用
1,像UICollectionViewLayout一样使用就行- (UICollectionView *)collecionView { if (!_collecionView) { ZMCollectionViewFlowLayout *layout = [[ZMCollectionViewFlowLayout alloc] init]; layout.delegate = self; layout.interval = 1; layout.columnNumbers = 3; // 显示的列数 _collecionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; _collecionView.dataSource = self; _collecionView.delegate = self; } return _collecionView; }
2,实现其代理
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.dataList[indexPath.row].floatValue; // 返回每个item的高度
}
- 结果