一.说明
新建一个Single View Application,删除main.stroryboard中原有的控制器,拖入一个新的CollectionViewController;
在原有的ViewController.h文件中,将继承改成UICollectionViewController,且绑定CollectionViewController.
在原型cell中拖入imageView控件和label控件,并设置相应约束.创建相应继承于UICollectionViewCell的cell文件,并绑定原型cell,设置可重用ID
导入相应plist文件和图片(如果只是做练习,可以自己创建个plist文件,设置其不同宽高即可,图片用颜色代替)
因为每张图片的尺寸大小都不同,则每个item的布局都不相同,故创建继承与UICollectionViewFlowLayout的文件,并绑定main.storyboard的Flow Layout
所有的文件列表如下:
二.代码实现
// // HBGood.h 文件中 // 4.30 CollectionView瀑布流 #import <Foundation/Foundation.h> @interface HBGood : NSObject //根据plist文件创建相应实型 @property (nonatomic,copy)NSString *icon; @property (nonatomic,copy)NSString *price; @property (nonatomic,assign)float height; @property (nonatomic,assign)float width; //构造方法 -(instancetype)initWithDict:(NSDictionary *)dict; +(instancetype)goodWithDict:(NSDictionary *)dict; @end
// // HBGood.m 文件中 // 4.30 CollectionView瀑布流 #import "HBGood.h" @implementation HBGood -(instancetype)initWithDict:(NSDictionary *)dict { if (self = [super init]) { [self setValuesForKeysWithDictionary:dict]; } return self; } +(instancetype)goodWithDict:(NSDictionary *)dict { return [[self alloc]initWithDict:dict]; } @end
// // HBGoodCell.h 文件中 // #import <UIKit/UIKit.h> #import "HBGood.h" @interface HBGoodCell : UICollectionViewCell //设置HBGood属性,传入值时为cell内容赋值 @property (strong,nonatomic) HBGood* good; @end
// // HBGoodCell.m 文件中 // 4.30 CollectionView瀑布流 #import "HBGoodCell.h" @interface HBGoodCell () //cell中的图片接口 @property (weak, nonatomic) IBOutlet UIImageView *iconView; //cell中的文字接口 @property (weak, nonatomic) IBOutlet UILabel *priceLbl; @end @implementation HBGoodCell -(void)setGood:(HBGood *)good{ _good = good; self.iconView.image = [UIImage imageNamed:good.icon]; self.priceLbl.text = [NSString stringWithFormat:@"价格 : %@",good.price]; } @end
// // HBGoodFlowLayout.h 文件中 // 4.30 CollectionView瀑布流 #import <UIKit/UIKit.h> @interface HBGoodFlowLayout : UICollectionViewFlowLayout //导入所有的good,从而计算出所有good的布局 @property (strong,nonatomic)NSArray *goods; //设立一个列数属性,能从外部指定分为几列 @property (nonatomic,assign)int colNum; @end
/* HBGoodFlowLayout.m 文件中 瀑布流的思想: 每个商品都有其对应的宽高比,故使其宽度相同,对应的item高度就会不同,通过设置对应的layout 使其相互错开. 宽度右设定的列数决定 */ #import "HBGoodFlowLayout.h" #import "HBGood.h" @interface HBGoodFlowLayout () //该属性用来存储所有item的布局信息 @property (strong,nonatomic)NSMutableArray *allAttributes; //存储底部最大Y值来设定滚动范围 @property (assign,nonatomic)float maxY; @end @implementation HBGoodFlowLayout //初始化attributes -(NSMutableArray *)allAttributes{ if (_allAttributes == nil) { _allAttributes = [NSMutableArray array]; } return _allAttributes; } //改写系统准备布局的方法 -(void)prepareLayout{ /* 思路:建立一个数组,其个数等于列数,用来存储每列的下一个元素的Y值,取出Y值后立即更新到下一列的Y值 */ float nextColYs[self.colNum]; //初始化该数组 for (int i = 0; i<self.colNum; i++) { nextColYs[i] = 0; } CGFloat totalW = self.collectionView.frame.size.width; //计算所有商品宽度 CGFloat goodW = (totalW - self.sectionInset.left - self.sectionInset.right - (self.colNum - 1) * self.minimumInteritemSpacing) / self.colNum; //计算每个商品的frame for (int i = 0; i<self.goods.count; i++) { HBGood *good = self.goods[i]; //数组索引 int index = i % self.colNum;//计算按比例缩放后的高 CGFloat goodH = goodW * (good.height / good.width); //从数组中取出对应的Y值 CGFloat goodY = nextColYs[index]; //当前商品x值 = 屏幕左边间距 + 左边的商品个数 * (商品宽度 + 商品间隔) CGFloat goodX = index * (self.minimumInteritemSpacing + goodW) + self.sectionInset.left; //创建collectionViewLayoutAttributes对象来存储每个商品对应的frame NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attr.frame = CGRectMake(goodX, goodY, goodW, goodH); //加入allAttributes数组中 [self.allAttributes addObject:attr]; //更新数组中的下一个Y值 nextColYs[index] = CGRectGetMaxY(attr.frame) + self.minimumLineSpacing; } //所有商品循环完后,拿出最大的Y值作为滚动范围 self.maxY = [self getMax:nextColYs]; } //获得数组中做大值方法 -(float)getMax:(float *)arr{ float max = arr[0]; for (int i = 1; i<self.colNum; i++) { if (max < arr[i]) { max = arr[i]; } } return max; } //该方法返回所有元素的布局信息 -(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{ return self.allAttributes; } //返回滚动范围 -(CGSize)collectionViewContentSize{ return CGSizeMake(0, self.maxY); } @end
// // ViewController.h 文件中 // 4.30 CollectionView瀑布流 // #import <UIKit/UIKit.h> @interface ViewController : UICollectionViewController @end
// // ViewController.m 文件中 // 4.30 CollectionView瀑布流 #import "ViewController.h" #import "HBGood.h" #import "HBGoodCell.h" #import "HBGoodFlowLayout.h" @interface ViewController () //创建flowLayout的输出接口 @property (weak, nonatomic) IBOutlet HBGoodFlowLayout *flowLayout; @property (strong,nonatomic)NSArray *goods; @end @implementation ViewController static NSString *ID = @"GOOD"; #pragma mark - 懒加载实现 -(NSArray *)goods{ if (_goods == nil) { NSString *path = [[NSBundle mainBundle]pathForResource:@"water_fall01" ofType:@"plist"]; NSArray *arr = [NSArray arrayWithContentsOfFile:path]; NSMutableArray *mtArr= [NSMutableArray array]; for (NSDictionary *dict in arr) { HBGood *good = [HBGood goodWithDict:dict]; [mtArr addObject:good]; } _goods = mtArr; } return _goods; } #pragma mark - ViewDidLoad - (void)viewDidLoad { [super viewDidLoad]; //设置列数 self.flowLayout.colNum = 4; //设置CollectionView背景色 self.collectionView.backgroundColor = [UIColor whiteColor]; //将所有的商品信息赋给layout,让其计算出相应布局 self.flowLayout.goods = self.goods; } #pragma mark - 设定item数 -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return self.goods.count; } #pragma mark - 设定item内容 -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ HBGoodCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath]; cell.good = self.goods[indexPath.item]; return cell; } @end
以上,程序基本实现完毕.
三.优化
当用以上代码时,可能会出现如图分配不均的情况
可通过如下修改HBGoodFlowLayout.m中代码解决该问题
// // HBGoodFlowLayout.m文件中 // -(void)prepareLayout{ ...... for (int i = 0; i<self.goods.count; i++) { HBGood *good = self.goods[i]; //数组索引 int index = i % self.colNum; //为了解决分配不平均,此时数组的索引就不能直接取值,而是通过每次取得最小Y值对应的索引 int index = [self getMinIndex:nextColYs]; .... } //添加获得数组中最小值的索引方法 -(int)getMinIndex:(float *)arr{ float min = arr[0]; int index = 0; for (int i = 1; i<self.colNum; i++) { if (min > arr[i]) { min = arr[i]; index = i; } } return index; } ...