iOS 瀑布流布局实现详解

新建文件继承自UICollectionViewLayout
.h内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@class WaterFlowLayout;
@protocol WaterFlowLayoutDelegate <NSObject>

//使用delegate取得每一个Cell的高度
- (CGFloat)waterFlow:(WaterFlowLayout *)layout heightForCellAtIndexPath:(NSIndexPath *)indexPath;

@end

@interface WaterFlowLayout : UICollectionViewLayout

//声明协议
@property (nonatomic, weak) id <WaterFlowLayoutDelegate> delegate;
//确定列数
@property (nonatomic, assign) NSInteger colum;
//确定内边距
@property (nonatomic, assign) UIEdgeInsets insetSpace;
//确定每个cell之间的距离
@property (nonatomic, assign) NSInteger distance;

@end

.m实现内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@interface WaterFlowLayout ()
//存储列高的数组
@property (nonatomic, strong) NSMutableArray *columHeightArr;
//存储所有cell的尺寸信息
@property (nonatomic, strong) NSMutableArray *cellFrameArr;

@end

@implementation WaterFlowLayout

//colum的set方法
- (void)setColum:(NSInteger)colum{
if (_colum != colum) {
_colum = colum;
//将之前的布局信息失效,重新布局
[self invalidateLayout];
}
}

//distance的set方法
- (void)setDistance:(NSInteger)distance{
if (_distance != distance) {
_distance = distance;
[self invalidateLayout];
}
}

//insetSpace的set方法
- (void)setInsetSpace:(UIEdgeInsets)insetSpace{
if (!UIEdgeInsetsEqualToEdgeInsets(_insetSpace, insetSpace)) {
_insetSpace = insetSpace;
[self invalidateLayout];
}
}

//自定义layout需要重写下面的几个方法
//准备布局,将item的位置信息计算出来
- (void)prepareLayout{
//将位置信息和高度信息的数组实例化
[self initDataArray];
//初始化每一列的初始高度
[self initColumHeightArray];
//初始化计算出全部cell的高度,并且存入数组
[self initAllCellHeight];
}

//将位置信息和高度信息的数组实例化
- (void)initDataArray{
//记录当前每一列的高度,所以我们只需要列数的空间就够了。
_columHeightArr = [NSMutableArray arrayWithCapacity:_colum];
//记录所有cell的尺寸信息
_cellFrameArr = [NSMutableArray arrayWithCapacity:0];
}

//初始化每一列的初始高度
- (void)initColumHeightArray{
for (int i = 0; i < _colum; i++) {
[_columHeightArr addObject:@(_insetSpace.top)];
}
}

//初始化计算出全部cell的高度,并且存入数组
- (void)initAllCellHeight{
//拿出第一组的全部cell的数量
NSInteger allCellNumber = [self.collectionView numberOfItemsInSection:0];
//取得整个collectionView的宽度
CGFloat totalWidth = self.collectionView.frame.size.width;
//取得一行中Cell的总宽度
CGFloat itemAllWidth = totalWidth - _insetSpace.left - _insetSpace.right - _distance * (_colum - 1);
//取得每一个cell的宽度
CGFloat width = itemAllWidth/_colum;
//循环计算每一个cell的高度并且将位置信息添加到数组中
for (int i = 0; i < allCellNumber; i++) {
//拿到当前的列的信息
NSInteger currentColum = [self getShortColum];
//x偏移就是用当前的列去乘以宽度和间距,并且加上内边距
CGFloat xOffset = _insetSpace.left + currentColum * (width + _distance);
//制造索引路径
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//取得y偏移
CGFloat yOffset = [[_columHeightArr objectAtIndex:currentColum] floatValue] + _distance;
//取得高度,由实现协议者提供
CGFloat height = 0.f;
if (_delegate && [_delegate respondsToSelector:@selector(waterFlow:heightForCellAtIndexPath:)]) {
height = [_delegate waterFlow:self heightForCellAtIndexPath:indexPath];
}
//整理cell的尺寸信息
CGRect frame = CGRectMake(xOffset, yOffset, width, height);
//attributes是用来存储当前indexPath的cell的位置信息的
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = frame;
//将位置信息添加到cell尺寸数组中
[_cellFrameArr addObject:attributes];
//改变当前列的高度
_columHeightArr[currentColum] = @(frame.size.height + frame.origin.y);
}
}

//取得当前cell的尺寸
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
return [_cellFrameArr objectAtIndex:indexPath.item];
}

//根据rect去找出需要布局的cell的位置信息
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
//用来存储可以展示的cell的位置信息
NSMutableArray *temp = [NSMutableArray arrayWithCapacity:0];
for (UICollectionViewLayoutAttributes *attributes in _cellFrameArr) {
//如果取出的位置信息,在rect的范围内,就将这个位置信息,装入数组中。
if (CGRectIntersectsRect(attributes.frame, rect)) {
[temp addObject:attributes];
}
}
return temp;
}

//指定collection的contentSize
- (CGSize)collectionViewContentSize{
//内容宽度指定为collectionView的宽度(横向不发生滚动)
CGFloat width = self.collectionView.frame.size.width;
//取出最长的列,将其高度定位长度
CGFloat height = [self getLongColum];
return CGSizeMake(width, height);
}

- (CGFloat)getLongColum{
//记录当前最长的列号
__block NSInteger currentColum = 0;
//假设最长的列高度为0
__block CGFloat longHeight = 0;
//枚举数组中的元素
[_columHeightArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj floatValue] > longHeight) {
longHeight = [obj floatValue];
currentColum = idx;
}
}];
return longHeight + _insetSpace.bottom;
}

//取得最短的列
- (NSInteger)getShortColum{
//记录当前最短的列号
__block NSInteger currentColum = 0;
//假设最短的列高度为float的最大值
__block CGFloat shortHeight = MAXFLOAT;
//枚举数组中的元素
[_columHeightArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj floatValue] < shortHeight) {
shortHeight = [obj floatValue];
currentColum = idx;
}
}];
return currentColum;
}

@end

-------------本文结束感谢您的阅读-------------
最近的文章

iOS 悬浮按钮

新建继承于UIWindow的类.h文件如下123456789101112131415typedef void(^ClickBlock)(NSInteger i);@interface GSLFloatingView : UIWindow@property (nonatomic, copy) Clic …

继续阅读
更早的文章

iOS 动画-购物车动画为例

设定动画设定动画的属性和说明 属性 说明 duration 动画的时长 repeatCount 重复的次数。不停重复设置为 HUGE_VALF repeatDuration 设置动画的时间。在该时间内动画一直执行,不计次数。 beginTime 指定动画开始的时间。从开始延 …

继续阅读