相信所有人都应该知道瀑布流是用集合视图UICollectionView实现的,这里呢我们先回忆下集合视图最常用的几个方法
/**
* 设置某个Item是否可以移动
*/
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath ;
/**
*移动item的时候调用的方法
*/
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath ;
//拼音索引列表
- (nullable NSArray<NSString *> *)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<UICollectionViewLayoutAttributes *> *)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<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.attributesArray;
}
@end
网友评论