iOS GZWaterfall任何形式的瀑布流

概述

使用UICollectionView可以布局各种各样的瀑布流,下面我写了几种不同布局的瀑布流样式

详细

首先,将所有的类型展示给大家;

屏幕快照 2017-08-09 14.42.22.png

 

 

 

上面图片中展示的样式在Demo中都有实现。

 

一、项目结构

屏幕快照 2017-08-08 10.15.13.png

对于我们要实现的各种各样的 collectionView,根据不同的需求设置不同的列数 ,列边距,行边距,collectionView边距

二、程序实现

1、随机瀑布流

屏幕快照 2017-08-09 15.48.30.png

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#pragma mark - 创建collectionView
- (void)setupCollectionView
{
    GZFallsLayout *fallsLayout = [[GZFallsLayout alloc] init];
    fallsLayout.delegate = self;
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:fallsLayout];
    [self.view addSubview:collectionView];
    _collectionView = collectionView;
    collectionView.dataSource = self;
    [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GZShopCell class]) bundle:nil] forCellWithReuseIdentifier:ID];
}
 
#pragma mark - 创建上下拉刷新
- (void)setupRefresh
{
    self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)];
    self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)];
    self.collectionView.backgroundColor = [UIColor whiteColor];
    [self.collectionView.mj_header beginRefreshing];
}
 
#pragma mark - 加载下拉数据
- (void)loadNewShops
{
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSArray *shops = [GZShop mj_objectArrayWithFilename:@"1.plist"];
        [weakSelf.shops removeAllObjects];
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf.collectionView reloadData];
            [weakSelf.shops addObjectsFromArray:shops];
            [weakSelf.collectionView.mj_header endRefreshing];
            [weakSelf.collectionView reloadData];
        });
    });
}
 
#pragma mark - 加载上拉数据
- (void)loadMoreShops
{
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSArray *shops = [GZShop mj_objectArrayWithFilename:@"1.plist"];
        [weakSelf.shops addObjectsFromArray:shops];
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf.collectionView.mj_footer endRefreshing];
            [weakSelf.collectionView reloadData];
        });
    });
}
 
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}
 
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    self.collectionView.mj_footer.hidden = self.shops.count == 0;
    return self.shops.count;
}
 
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    GZShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
    if (self.shops && self.shops.count >= indexPath.item+1) cell.shop = self.shops[indexPath.item];
    return cell;
}
 
- (CGFloat)columnMarginInFallsLayout:(GZFallsLayout *)fallsLayout
{
    return 5;
}
 
- (CGFloat)rowMarginInFallsLayout:(GZFallsLayout *)fallsLayout
{
    return 5;
}
 
- (CGFloat)columnCountInFallsLayout:(GZFallsLayout *)fallsLayout
{
    return 4;
}
 
- (UIEdgeInsets)edgeInsetsInFallsLayout:(GZFallsLayout *)fallsLayout
{
    return UIEdgeInsetsMake(0, 10, 20, 10);
}
 
- (NSMutableArray *)shops
{
    if (!_shops) {
        _shops = [NSMutableArray array];
    }
    return _shops;
}
 
// 计算布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
     
    // 每个collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;
    // 每个cell的宽度
    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right -
                 self.columnMargin * (self.columnCount - 1)) / self.columnCount;
    // cell的高度
    NSUInteger randomOfHeight = arc4random() % 100;
    CGFloat h = w * (randomOfHeight >= 50 ? 250 : 320) / 200;
     
    // cell应该拼接的列数
    NSInteger destColumn = 0;
     
    // 高度最小的列数高度
    CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
    // 获取高度最小的列数
    for (NSInteger i = 1; i < self.columnCount; i++) {
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];
        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }
     
    // 计算cell的x
    CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
    // 计算cell的y
    CGFloat y = minColumnHeight;
    if (y != self.edgeInsets.top) {
        y += self.rowMargin;
    }
     
    // 随机数,用来随机生成大尺寸cell
    NSUInteger randomOfWhetherDouble = arc4random() % 100;
    // 判断是否放大
    if (destColumn < self.columnCount - 1                               // 放大的列数不能是最后一列(最后一列方法超出屏幕)
        && _noneDoubleTime >= 1                                         // 如果前个cell有放大就不放大,防止连续出现两个放大
        && (randomOfWhetherDouble >= 45 || _noneDoubleTime >= 8)        // 45%几率可能放大,如果累计8次没有放大,那么满足放大条件就放大
        && [self.columnHeights[destColumn] doubleValue] == [self.columnHeights[destColumn + 1] doubleValue] // 当前列的顶部和下一列的顶部要对齐
        && _lastDoubleIndex != destColumn) {             // 最后一次放大的列不等当前列,防止出现连续两列出现放大不美观
        _noneDoubleTime = 0;
        _lastDoubleIndex = destColumn;
        // 重定义当前cell的布局:宽度*2,高度*2
        attrs.frame = CGRectMake(x, y, w * 2 + self.columnMargin, h * 2 + self.rowMargin);
        // 当前cell列的高度就是当前cell的最大Y值
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        // 当前cell列下一列的高度也是当前cell的最大Y值,因为cell宽度*2,占两列
        self.columnHeights[destColumn + 1] = @(CGRectGetMaxY(attrs.frame));
    } else {
        // 正常cell的布局
        if (_noneDoubleTime <= 3 || _lastFixIndex == destColumn) {                     // 如果没有放大次数小于3且当前列等于上次矫正的列,就不矫正
            attrs.frame = CGRectMake(x, y, w, h);
        } else if (self.columnHeights.count > destColumn + 1                         // 越界判断
            && y + h - [self.columnHeights[destColumn + 1] doubleValue] < w * 0.1) { // 当前cell填充后和上一列的高度偏差不超过cell最大高度的10%,就和下一列对齐
            attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn + 1] doubleValue] - y);
            _lastFixIndex = destColumn;
        } else if (destColumn >= 1                                                   // 越界判断
                   && y + h - [self.columnHeights[destColumn - 1] doubleValue] < w * 0.1) { // 当前cell填充后和上上列的高度偏差不超过cell最大高度的10%,就和下一列对齐
            attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn - 1] doubleValue] - y);
            _lastFixIndex = destColumn;
        } else {
            attrs.frame = CGRectMake(x, y, w, h);
        }
        // 当前cell列的高度就是当前cell的最大Y值
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        _noneDoubleTime += 1;
    }
    // 返回计算获取的布局
    return attrs;
}

2、规则瀑布流

屏幕快照 2017-08-09 15.56.19.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 计算布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    if (indexPath.item == 0) {
        attrs.frame = CGRectMake(0, 0, ([UIScreen mainScreen].bounds.size.width - 4)/3*2 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3*2 +2);
    }else if (indexPath.item == 1){
        attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, 0, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3);
    }else if (indexPath.item == 2){
        attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3);
    }else if (indexPath.item == 3){
        attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 + 4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3);
    }else if (indexPath.item == 4){
        attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3);
    }else{
        attrs.frame = CGRectMake(0, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3);
    }
    // 返回计算获取的布局
    return attrs;
}

3、简单两排瀑布流

屏幕快照 2017-08-09 15.58.30.png

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
-(CGSize)collectionViewContentSize
{
    //计算整个contentsize的大小
    __block CGFloat height=0;
    [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if([obj floatValue]>height)
        {
            height=[obj floatValue];
        }
    }];
    return CGSizeMake(self.collectionView.bounds.size.width, height);
}
 
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //计算每一个item的相关属性
    UICollectionViewLayoutAttributes *attr=[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    int index=0;
    if([arr[0] floatValue]<[arr[1] floatValue])
    {
        index=0;
    }
    else
    {
        index=1;
    }
    CGFloat width=self.collectionView.bounds.size.width/2;
    CGFloat height=arc4random()%200+100;
    CGFloat left=index*width;
    CGFloat top=[arr[index] floatValue];
    CGRect frame=CGRectMake(left, top, width, height);
    attr.frame=frame;
    arr[index]=@([arr[index] floatValue]+height);
    return attr;
}

4、两排瀑布流

屏幕快照 2017-08-09 15.59.10.png

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
-(void)prepareLayout{
 
    _attributeArray = [NSMutableArray array];
    [super prepareLayout];
    float WIDTH = ([UIScreen mainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2;
    CGFloat colHight[2] = {self.sectionInset.top,self.sectionInset.bottom};
    for (int i=0; i<_itemCount; i++) {
         
        NSIndexPath * index = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes * attribute =  [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
        CGFloat height = arc4random()%150+40;
        int width = 0;
        if (colHight[0]<colHight[1]) {
            colHight[0] = colHight[0]+height+self.minimumLineSpacing;
            width = 0;
        }else{
            colHight[1] = colHight[1]+height+self.minimumLineSpacing;
            width = 1;
        }
        attribute.frame = CGRectMake(self.sectionInset.left+(self.minimumInteritemSpacing+WIDTH)*width, colHight[width]-height-self.minimumLineSpacing, WIDTH, height);
        [_attributeArray addObject:attribute];
         
    }
    if (colHight[0]>colHight[1]) {
        self.itemSize = CGSizeMake(WIDTH, (colHight[0]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
    }else{
        self.itemSize = CGSizeMake(WIDTH, (colHight[1]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
    }
 
}

5、环形瀑布流

屏幕快照 2017-08-09 16.00.22.png

1
2
3
4
5
6
7
8
9
10
11
12
_itemCount = (int)[self.collectionView numberOfItemsInSection:0];
   _attributeArray = [NSMutableArray array];
   CGFloat radius  =MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height)/2;
   CGPoint center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2);
   for (int i=0; i<_itemCount; i++) {
       UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
       attris.size = CGSizeMake(50, 50);
       float x = center.x+cosf(2*M_PI/_itemCount*i)*(radius-25);
       float y = center.y+sinf(2*M_PI/_itemCount*i)*(radius-25);
       attris.center = CGPointMake(x, y);
       [_attributeArray addObject:attris];
   }

6、立方瀑布流

屏幕快照 2017-08-09 16.02.21.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    //创建一个item布局属性类
    UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //获取item的个数
    int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
    //设置每个item的大小为260*100
    atti.size = CGSizeMake(260, 100);
    atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);
    CATransform3D tran3d = CATransform3DIdentity;
    tran3d.m34 = -1/2000.0;
    CGFloat radius = 50/tanf(M_PI*2/itemCounts/2);
//     CGFloat angle = (float)(indexPath.row)/itemCounts*M_PI*2;
    //获取当前的偏移量
    float offset = self.collectionView.contentOffset.y;
    //在角度设置上,添加一个偏移角度
    float angleOffset = offset/self.collectionView.frame.size.height;
    CGFloat angle = (float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2;
    tran3d = CATransform3DRotate(tran3d, angle, 1.0, 0, 0);
    tran3d = CATransform3DTranslate(tran3d, 0, 0, radius);
    //进行设置
    atti.transform3D = tran3d;
    return atti;
}

7、球形瀑布流

屏幕快照 2017-08-09 16.05.13.png

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
UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
   //获取item的个数
   int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
   atti.center = CGPointMake(self.collectionView.frame.size.width/2+self.collectionView.contentOffset.x, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);
   atti.size = CGSizeMake(30, 30);
    
   CATransform3D trans3D = CATransform3DIdentity;
   trans3D.m34 = -1/900.0;
    
   CGFloat radius = 15/tanf(M_PI*2/itemCounts/2);
   //根据偏移量 改变角度
   //添加了一个x的偏移量
   float offsety = self.collectionView.contentOffset.y;
   float offsetx = self.collectionView.contentOffset.x;
   //分别计算偏移的角度
   float angleOffsety = offsety/self.collectionView.frame.size.height;
   float angleOffsetx = offsetx/self.collectionView.frame.size.width;
   CGFloat angle1 = (float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2;
   //x,y的默认方向相反
   CGFloat angle2 = (float)(indexPath.row+angleOffsetx-1)/itemCounts*M_PI*2;
   //这里我们进行四个方向的排列
   if (indexPath.row%4==1) {
       trans3D = CATransform3DRotate(trans3D, angle1, 1.0,0, 0);
   }else if(indexPath.row%4==2){
       trans3D = CATransform3DRotate(trans3D, angle2, 0, 1, 0);
   }else if(indexPath.row%4==3){
       trans3D = CATransform3DRotate(trans3D, angle1, 0.5,0.5, 0);
   }else{
       trans3D = CATransform3DRotate(trans3D, angle1, 0.5,-0.5,0);
   }
    
   trans3D = CATransform3DTranslate(trans3D, 0, 0, radius);
    
   atti.transform3D = trans3D;

三、运行效果

屏幕快照 2017-08-09 15.48.30.png屏幕快照 2017-08-09 15.56.19.png屏幕快照 2017-08-09 16.02.21.png屏幕快照 2017-08-09 16.05.13.png屏幕快照 2017-08-09 16.00.22.png屏幕快照 2017-08-09 15.59.10.png屏幕快照 2017-08-09 15.58.30.png

 

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

posted on   demo例子集  阅读(362)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示