IOS 瀑布流UICollectionView实现

IOS 瀑布流UICollectionView实现


在实现瀑布流之前先来看看瀑布流的雏形(此方法的雏形 UICollectionView)

对于UICollectionView我们有几点注意事项

  • 它和tableView不一样,ContentView的内容完全需要我们自己去添加。
  • 它与tableview相比,他的初始化需要FlowLayout并且大部分操作在其上。
  • UIcollectionView的实用性极强,虽然有时他并不是最好的解决方案,但是它可以很灵活的实现各种效果。

图(一)

如图,模拟器上展示的是很多方格,但是值得注意的是他们是有规则的。

虽然看上去很整洁但是并不美观。

我们所说的要实现瀑布流就是要实现它的不整洁,但是规律(这里我说的是规律)

正题

前面说了UIcollectionView的大部分操作在FlowLayout上,当然也包括格局部署。

为了实现瀑布流我们所要实现的便是改变他的格局部署。

在写代码前先确定一下实现思想。

  • 需要什么???
    • 首先我们需要确定瀑布流的显示风格
    • 然后根据确定好的风格进行整体设计
    • 最后通过细节的处理完善代码
      • 我们需要什么样的风格???
        • 我们需要的是实现将上面图片中的布局改变为不等高的效果
        • 说的俗一点就是像名字一样,像瀑布流水一样
      • 整体该如何设计???
        • 整体采用与上面图片一样的设计方法,每个模块都是一个cell
        • 确保最上面一行的cell的y值相同(美观)
        • 确保不不会出现一列特别长,一列特别短的效果
      • 初步细节有哪些???
        • 因为每个cell的height不同,所以我们要考虑放置的顺序应该是什么
        • 精简代码(这是每个项目必须注意的)

实现效果

代码

下面是实现的代码部分(不提供demo了 很简单)

我在注释中简单介绍。

---

//
//  ViewController.m
//  CX-瀑布流UIcollectionView实现
//
//  Created by ma c on 16/4/8.
//  Copyright © 2016年 bjsxt. All rights reserved.
//

#import "ViewController.h"
#import "CXCollectionViewCell.h"
#import "CXCollectionViewLayout.h"

static NSString * identifier = @"cellID";

@interface ViewController ()<UICollectionViewDataSource>
//所要展示的UICollectionView
@property (nonatomic, strong) UICollectionView * collectionView;

@end

@implementation ViewController

#pragma mark - <懒加载>
- (UICollectionView *)collectionView {
    if (!_collectionView) {
        //初始化我们自定义的flowLayout
        CXCollectionViewLayout * flowLayout = [[CXCollectionViewLayout alloc]init];
        //初始化collectionView
        _collectionView = [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
        //设置数据源(collectionView的命根子)
        _collectionView.dataSource = self;
        //注册我们自定义的cell
        [_collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CXCollectionViewCell class]) bundle:nil] forCellWithReuseIdentifier:identifier];
    }
    return _collectionView;
}


#pragma mark - <life>

- (void)viewDidLoad {
    [super viewDidLoad];
    //在self.view上添加---
    [self.view addSubview:self.collectionView];
}
#pragma mark - <UICollectionViewDataSource>
//这里返回的是item的个数 返回100
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    
    return 100;
}
//这里返回的是cell 我们可以在这里进行一些简单的操作
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    CXCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    //为了瀑布流的实现细节我们添加的Label
    
    cell.label.text = [NSString stringWithFormat:@"%zd",indexPath.item];
    //cell的背景色
    cell.backgroundColor = [UIColor orangeColor];
    
    return cell;
}

@end

---

//
//  CXCollectionViewLayout.m
//  CX-瀑布流UIcollectionView实现
//
//  Created by ma c on 16/4/8.
//  Copyright © 2016年 bjsxt. All rights reserved.
//

#import "CXCollectionViewLayout.h"

//瀑布流的列数
static NSInteger CXcolumnCount = 3;
//瀑布流的内边距
static UIEdgeInsets CXdefaultEdgeInsets = {20,15,10,15};
//cell的列间距
static NSInteger CXcolumnMagin = 10;
//cell的行间距
static NSInteger CXrowMagin = 10;

@interface CXCollectionViewLayout ()

//存放所有cell 的布局属性
@property (nonatomic, strong) NSMutableArray * CXattrsArray;
//缩放所有列的高度
@property (nonatomic, strong) NSMutableArray * CXcolumnHeights;

@end

@implementation CXCollectionViewLayout

#pragma mark - <懒加载>
- (NSMutableArray *)CXattrsArray{
    if (!_CXattrsArray) {
        _CXattrsArray = [NSMutableArray array];
    }
    return _CXattrsArray;
}

- (NSMutableArray *)CXcolumnHeights{
    if (!_CXcolumnHeights) {
        _CXcolumnHeights = [NSMutableArray array];
    }
    return _CXcolumnHeights;
}
#pragma mark - <准备布局>
//准备布局(布局前自动执行)
- (void) prepareLayout{
    //重写此方法一定要记得super
    [super prepareLayout];
    
    //在实际操作中我们的数据并不会固定不变的,因此我们每次布局前最好要清空之前存储的属性
    //清空存放所有列的高度
    //清空存放所有cell的不去属性
    [self.CXcolumnHeights removeAllObjects];
    [self.CXattrsArray removeAllObjects];
    //首先为第一行的cell附高度
    for (NSInteger i = 0; i < CXcolumnCount; i ++) {
        //数组里只能存放对象
        [self.CXcolumnHeights addObject:@(CXdefaultEdgeInsets.top)];
    }
    //下面开始创建每一个cell的布局属性 并且添加到存储cell布局属性的数组中
    //cell总个数 因为这里只要一个section
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i ++) {
        // 创建位置 即indexPath
        NSIndexPath * indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        //获取indexPath对应的cell布局属性
        UICollectionViewLayoutAttributes * attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
        //把获取到的布局属性添加到数组中
        [self.CXattrsArray addObject:attributes];
    }
    //准备布局的工作到这里就结束了
}
//返回所有cell布局属性 及整体cell 的排布
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return self.CXattrsArray;
}
//返回cell 的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    //创建布局属性
    UICollectionViewLayoutAttributes * CXattributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    //获取collectionView 的宽
    CGFloat collectionViewWidth = self.collectionView.frame.size.width;
    //下面的一部分是获取cell的frame(布局属性)
    CGFloat width;
    CGFloat height;
    CGFloat X;
    CGFloat Y;
    //获取width
    width = (collectionViewWidth - CXdefaultEdgeInsets.left - CXdefaultEdgeInsets.right - (CXcolumnCount - 1) * CXcolumnMagin) / CXcolumnCount;
    //获取height
    //在实际开发中heigh并不是真正的随机 而是根据数据来决定height 在这里展示初步的介绍其原理 因此采用大于100小于150的随机数
    height = 100 + arc4random_uniform(50);
    //获取X (瀑布流的实现重点就在cell的X,Y值获取)
    //设置一个列数的中间变量
    NSInteger tempColumn = 0;
    //设置高度小的中间变量 在这里我们把第0列的高度给他,这样可以减少循环次数,提高效率
    CGFloat minColumnHeight = [self.CXcolumnHeights[0] doubleValue];
    for (NSInteger i = 1; i < CXcolumnCount; i ++) {
        if (minColumnHeight > [self.CXcolumnHeights[i] doubleValue]) {
            minColumnHeight = [self.CXcolumnHeights[i] doubleValue];
            tempColumn = i;
        }
    }
    X = CXdefaultEdgeInsets.left + (width + CXcolumnMagin) * tempColumn;
    //获取Y
    Y = minColumnHeight;
    if (Y != CXdefaultEdgeInsets.top) {
        Y += CXrowMagin;
    }
    //设置cell的frame
    CXattributes.frame = CGRectMake(X, Y, width, height);
    //更新高度最矮的那列的高度
    self.CXcolumnHeights[tempColumn] = @(CGRectGetMaxY(CXattributes.frame));
    
    return CXattributes;
}
//返回collegeView的Content的大小
- (CGSize)collectionViewContentSize{
    //虽说返回的是大小,但是我们这里主要的是height
    CGFloat maxColumnHeight = [self.CXcolumnHeights[0] doubleValue];
    for (NSInteger i = 1; i < CXcolumnCount; i++) {

        CGFloat columnHeight = [self.CXcolumnHeights[i] doubleValue];
        
        if (maxColumnHeight < columnHeight) {
            maxColumnHeight = columnHeight;
        }
    }
    return CGSizeMake(0, maxColumnHeight + CXdefaultEdgeInsets.bottom);
    
}

@end

到此为止瀑布流的实现也就结束了。

在这里说明几点值得注意的地方。

  • 瀑布流中的cell排布顺势是根据当前列的高度有关的(例如:如果当前第三列是最短的,但是按正常情况下cell应该排在第一列,那么这个时候,新的cell会排在第三列,这是为了避免某一列高度特别长或某一列的高度特别短)
  • 在实际应用中通常cell的大小是根据数据的来处理的
  • UIcollectionView的content的高度是不确定的,因此我们要根据内容设定高度。
  • 当涉及到刷新的时候我们要注意cell的布局属性是否在新数据到来前清空了。
posted @ 2016-04-09 14:37  旭宝爱吃鱼  阅读(2042)  评论(1编辑  收藏  举报