自定义UICollectionViewLayout之瀑布流

目标效果

因为系统给我们提供的 UICollectionViewFlowLayout 布局类不能实现瀑布流的效果,如果我们想实现 瀑布流 的效果,需要自定义一个 UICollectionViewLayout  类,实现瀑布流效果。效果如右图。

依赖工具:

我们需要一个图片大小和图片地址的Josn数据, 和 SDWebImage图片加载的第三方工具

 

 

 

 

 

 

 

 

 

RootViewController.m

  1 #import "RootViewController.h"
  2 #import "DataModel.h"
  3 #import "WaterFlowLayout.h"
  4 #import "RootCell.h"
  5 #import "UIImageView+WebCache.h"
  6 
  7 @interface RootViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, WaterFlowLayoutDelegate>
  8 
  9 // 声明大数组存放所有的数据
 10 @property (nonatomic, strong) NSMutableArray *allDataArray;
 11 
 12 // 定义collectionView
 13 @property (nonatomic, strong) UICollectionView *collectionView;
 14 
 15 @end
 16 
 17 @implementation RootViewController
 18 
 19 // 懒加载
 20 - (NSMutableArray *)allDataArray {
 21     if (!_allDataArray) {
 22         _allDataArray = [NSMutableArray array];
 23     }
 24     return _allDataArray;
 25 }
 26 
 27 - (void)viewDidLoad {
 28     [super viewDidLoad];
 29     // Do any additional setup after loading the view.
 30     
 31     // 读取数据
 32     [self loadData];
 33     
 34     // 初始化布局
 35     [self initLayout];
 36     
 37     // 注册cell
 38     [self.collectionView registerClass:[RootCell class] forCellWithReuseIdentifier:@"cell"];
 39 }
 40 
 41 // 初始化布局
 42 - (void)initLayout {
 43     
 44     // 1.创建UICollectionView的布局样式对象
 45     WaterFlowLayout *water = [[WaterFlowLayout alloc] init];
 46     CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3;
 47     water.itemSize = CGSizeMake(width, width);
 48     // 设置内边距
 49     water.sectionInsets = UIEdgeInsetsMake(10, 10, 10, 10);
 50     // 设置间距
 51     water.spacing = 10;
 52     // 设置有多少列
 53     water.numberOfColumn = 3;
 54     // 设置代理
 55     water.delegate = self;
 56     
 57     // 2.布局UICollectionView
 58     self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:water];
 59     self.collectionView.delegate = self;
 60     self.collectionView.dataSource = self;
 61     self.collectionView.backgroundColor = [UIColor whiteColor];
 62     [self.view addSubview:self.collectionView];
 63     
 64 }
 65 
 66 // 读取数据
 67 - (void)loadData {
 68    
 69     // 第一步:获取文件路径
 70     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"json"];
 71     // 第二步:根据路径读取数据,转为NSData对象
 72     NSData *data = [NSData dataWithContentsOfFile:filePath];
 73     // 第三步:根据json格式解析数据
 74     NSArray *dataArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 75     
 76 //    NSLog(@"%@", dataArray);
 77     // 第四步:遍历数组,将数据转为model对象
 78     for (NSDictionary *dict in dataArray) {
 79         
 80         // 创建model对象
 81         DataModel *model = [[DataModel alloc] init];
 82         // 使用KVC给model赋值
 83         [model setValuesForKeysWithDictionary:dict];
 84         
 85         // 将model添加到大数组中
 86         [self.allDataArray addObject:model];
 87     }
 88 //    NSLog(@"%@", self.allDataArray);
 89 }
 90 
 91 // 设置分区个数
 92 - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
 93     return 1;
 94 }
 95 // 设置每个分区的item个数
 96 - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
 97     return self.allDataArray.count;
 98 }
 99 
100 // 返回每一个item的cell对象
101 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
102     
103     RootCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
104     
105     // 设置cell数据
106     
107     DataModel *model = self.allDataArray[indexPath.row];
108     NSURL *url = [NSURL URLWithString:model.thumbURL];
109     [cell.photoView sd_setImageWithURL:url];
110     cell.backgroundColor = [UIColor orangeColor];
111     return cell;
112 }
113 
114 // 实现代理方法返回每一个item的高度
115 - (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath {
116     
117     DataModel *model = self.allDataArray[indexPath.row];
118     CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3;
119     // 计算item高度
120     CGFloat height = model.height / model.width * width;
121     return height;
122     
123 }
124 
125 @end

主视图文件,主要用于处理数据和布局页面

 

RootCell.h

1 #import <UIKit/UIKit.h>
2 
3 @interface RootCell : UICollectionViewCell
4 
5 @property (nonatomic, strong) UIImageView *photoView;
6 
7 @end

RootCell.m

 1 #import "RootCell.h"
 2 
 3 @implementation RootCell
 4 
 5 - (instancetype)initWithFrame:(CGRect)frame {
 6     self = [super initWithFrame:frame];
 7     if (self) {
 8         self.photoView = [[UIImageView alloc] init];
 9         [self.contentView addSubview:self.photoView];
10     }
11     return self;
12 }
13 
14 - (void)layoutSubviews {
15     
16     self.photoView.frame = self.bounds;
17     
18 }
19 
20 @end

RootCell就是每一个Item的样式, 也就是一张张图片

 

WaterFlowLayout.h

 1 #import <UIKit/UIKit.h>
 2 
 3 @protocol WaterFlowLayoutDelegate <NSObject>
 4 
 5 // 返回每一个item的高度
 6 - (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath;
 7 
 8 @end
 9 
10 @interface WaterFlowLayout : UICollectionViewLayout
11 
12 // item的大小,需要根据这个获取宽度
13 @property (nonatomic, assign) CGSize itemSize;
14 
15 // 内边距的设置
16 @property (nonatomic, assign) UIEdgeInsets sectionInsets;
17 
18 // item的间距(这里水平方向和垂直方向的间距一样)
19 @property (nonatomic, assign) CGFloat spacing;
20 
21 // 列数
22 @property (nonatomic, assign) NSInteger numberOfColumn;
23 
24 // 设置代理,用于获取item的高度
25 @property (nonatomic, weak) id<WaterFlowLayoutDelegate>delegate;
26 
27 @end

WaterFlowLayout.m

  1 #import "WaterFlowLayout.h"
  2 
  3 @interface WaterFlowLayout ()
  4 
  5 // 声明私有属性
  6 // 保存一共有多少个item
  7 @property (nonatomic, assign) NSInteger numberOfItems;
  8 
  9 // 保存计算好的每一个item的信息
 10 @property (nonatomic, strong) NSMutableArray *itemAttributes;
 11 
 12 // 保存每一列的高度
 13 @property (nonatomic, strong) NSMutableArray *columnHeights;
 14 
 15 
 16 // 声明私有方法
 17 // 找到当前最长列
 18 - (NSInteger)indexOfHeightestColumn;
 19 
 20 // 找到当前最短列
 21 - (NSInteger)indexOfShortestColumn;
 22 
 23 @end
 24 
 25 @implementation WaterFlowLayout
 26 
 27 // 懒加载
 28 - (NSMutableArray *)itemAttributes {
 29     if (!_itemAttributes) {
 30         _itemAttributes = [NSMutableArray array];
 31     }
 32     return _itemAttributes;
 33 }
 34 
 35 - (NSMutableArray *)columnHeights {
 36     if (!_columnHeights) {
 37         _columnHeights = [NSMutableArray array];
 38     }
 39     return _columnHeights;
 40 }
 41 
 42 // 找到当前最长列
 43 - (NSInteger)indexOfHeightestColumn {
 44     // 记录最长列的下标
 45     NSInteger index = 0;
 46     // 记录最长列的高度
 47     CGFloat length = 0;
 48     for (int i = 0; i < self.columnHeights.count; i++) {
 49         // 将数组中的对象转为基本数值
 50         CGFloat currentLength = [self.columnHeights[i] floatValue];
 51         if (currentLength > length) {
 52             length = currentLength;
 53             index = i;
 54         }
 55     }
 56     return index;
 57 }
 58 
 59 // 找到当前最短列
 60 - (NSInteger)indexOfShortestColumn {
 61     NSInteger index = 0;
 62     CGFloat length = MAXFLOAT;
 63     for (int i = 0; i < self.columnHeights.count; i++) {
 64         
 65         CGFloat currentLength = [self.columnHeights[i] floatValue];
 66         if (currentLength < length) {
 67             length = currentLength;
 68             index = i;
 69         }
 70     }
 71     return index;
 72 }
 73 
 74 // 接下来重写三个方法
 75 
 76 // 准备布局,在这里计算每个item的frame
 77 - (void)prepareLayout {
 78     
 79     // 拿到一共有多少个item
 80     self.numberOfItems = [self.collectionView numberOfItemsInSection:0];
 81     // 每一列添加一个top高度
 82     for (int i = 0; i < self.numberOfColumn; i++) {
 83         // @() NSNumber字面量创建对象
 84         self.columnHeights[i] = @(self.sectionInsets.top);
 85     }
 86     
 87     // 依次为每个item设置位置信息,并存储在数组中
 88     for (int i = 0; i < self.numberOfItems; i++) {
 89         
 90         // 1.找到最短列的下标
 91         NSInteger shortestIndex = [self indexOfShortestColumn];
 92         // 2.计算X 目标X = 内边距左间距 + (宽 + item间距)*最短列下标
 93         CGFloat detalX = self.sectionInsets.left + shortestIndex * (self.itemSize.width + self.spacing);
 94         // 3.找到最短列的高度
 95         CGFloat height = [self.columnHeights[shortestIndex] floatValue];
 96         // 4.计算Y
 97         CGFloat detalY = height + self.spacing;
 98         // 5.创建indexPath
 99         NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
100         
101         // 6.调用代理方法计算高度
102         CGFloat itemHeight = 0;
103         if (_delegate && [_delegate respondsToSelector:@selector(heightForItemAtIndexPath:)]) {
104             itemHeight = [_delegate heightForItemAtIndexPath:indexPath];
105         }
106         
107         // 定义保存位置信息的对象
108         UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
109         
110         // 7.生成frame
111         attributes.frame = CGRectMake(detalX, detalY, self.itemSize.width, itemHeight);
112         // 8.将位置信息添加到数组中
113         [self.itemAttributes addObject:attributes];
114         
115         // 9.更新这一列的高度
116         self.columnHeights[shortestIndex] = @(detalY + itemHeight);
117     }
118     
119 }
120 
121 // 返回UICollectionView的大小
122 - (CGSize)collectionViewContentSize {
123     
124     // 求最高列的下标
125     NSInteger heightest = [self indexOfHeightestColumn];
126     // 最高列的高度
127     CGFloat height = [self.columnHeights[heightest] floatValue];
128     // 拿到collectionView的原始大小
129     CGSize size = self.collectionView.frame.size;
130     size.height = height + self.sectionInsets.bottom;
131     
132     return size;
133 }
134 
135 // 返回每一个item的位置信息
136 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
137     return self.itemAttributes;
138 }
139 
140 @end

WaterFlowLayout 就是我们自定义的 瀑布流 的文件

 

DataModel.h

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface DataModel : NSObject
 4 
 5 @property (nonatomic, copy) NSString *thumbURL;
 6 
 7 @property (nonatomic, assign) float width;
 8 
 9 @property (nonatomic, assign) float height;
10 
11 
12 @end

DataModel.m

 1 #import "DataModel.h"
 2 
 3 @implementation DataModel
 4 
 5 // 防崩
 6 - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
 7     
 8 }
 9 
10 @end

 

posted @ 2016-04-27 20:53  墨隐于非  阅读(529)  评论(6编辑  收藏  举报