-
github地址little-waterfall-framework-**
-
这里把整一个瀑布流小框架的实现思路写出来,今后也可以直接拿着用到项目中
-
瀑布流
- 每一个竖线往下,而且长短不一,像瀑布一样往下流,但每条线的长短或者速度都不一样。
-
假如用九宫格去布局像电商那种衣服之类的图片,是有很多缺点的
- 1.如果你使用填充,可能会导致图片变形
- 2.就算你不设置填充,也不可能保证能看到图片的全部
- 3.如果所有格子的大小都是一样,用户很有可能产生视觉疲劳
- 瀑布流大小不一样,流水一般,用户看起来会有新鲜感
-
注意点
- 我们要展示的图片比较多,就牵扯到循环利用了
- 图片可以滚动,说明是有一个ScrollView的
- 我们肯定是要在UIScrollView的身上做上千张上万张图片的循环利用
-
我们要实现瀑布流,有几种方案:
- 有三种方案
-
1.第一种:略显奇葩恶心的做法不推荐
- 先在上面搞一个ScrollView
- 再在UIScrollView上搞三个TableView
- 这样的话,每一�个TableView里面的图片的高度就和其它的TableView里面的图片没有关系了
- 但是TableView是能滚动的,这样感觉就对不上了,怎么做的呢?
- 禁止TableView的滚动事件,让后面的ScrollView可以滚动
-
但这样太麻烦了,还得搞三个数据源点击事件也分得很散,而且你的索引也是有问题的,要跨TableView算索引,很恶心的
-
-
第二种方案
- 往ScrollView添加图片,图片的高度不一样,但是宽度一样
- 如果我们展示上千张图片,意味着我们要创建上千个这样的View还要把它们排布好,这样的话内存就很大了,所以我们要做循环利用
- 比如说我们滚到一定位置的时候,有一些家伙不在屏幕里了,我们先把它放进一个集合里,或者是字典里面,或者是数组里面去,把它拿出来先
- 紧接着我们再往下滚,我们需要一个新的东西,怎么办?我们把前面的拿出来,改变它的高度,并且把里面的图片和文字�换掉就可以了
- 就像cell,结构不变,但是内容可以换掉,循环利用
- 但是这种方法也是挺恶心,你需要去不断的判断哪些控件离开了屏幕
-
这种方法以前有有些人用,但是现在基本没人都不用了
-
第三种方案
- 用CollectionView,里面的摆放的都是CollectionViewCell
- 这样的话就不需要做循环利用了,因为CollectionView会给你做,我们只需要告诉CollectionView这个cell 怎么摆,里面放些什么内容就行了
- �这样的话你就省了很大的一件事:循环利用(iOS6开始出现)
-
CollectionView默认的流水布局是不能实现我们的瀑布流的功能的,因为流水布局是�保证每一个家伙的高度都是一样的,但是瀑布流不是这样的,它是挑最短的一列去排的,哪一列最短,我就排在它的下面,这样的不停的补齐
-
所以我们不是继承自CollectionView的流水布局,而是继承自最根本的那个布局Layout,从最基本的布局CollectionViewLayout再写一套布局
- 基本骨架
- CollectionView继承自CollectionViewFlowLayout的时候,里面的的cell有一个默认size,会给你按流水布局来排布。你如果是继承自最根本的布局CollectionViewLayout,cell是不知道怎么去排的,需要你自己去排
- 今后你要继承自CollectionViewLayout这个家伙做事情,一般要实现下面四个方法:
- 自定义布局--CYWaterFlowLayout继承自CollectionViewLayout
- 在ViewController.m文件中,先创建好布局
#import "ViewController.h"
#import "CYWaterFlowLayout.h"
@interface ViewController ()<UICollectionViewDataSource>
@end
@implementation ViewController
static NSString * const CYShopId = @"shop";
- (void)viewDidLoad {
[super viewDidLoad];
// 创建布局
CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];
// 创建CollectionView
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
collectionView.dataSource = self;
[self.view addSubview:collectionView];
// 注册
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CYShopId];
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 50;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];
cell.backgroundColor = [UIColor orangeColor];
NSInteger tag = 10;
UILabel *label = (UILabel *)[cell.contentView viewWithTag:tag];
if (label == nil) {
label = [[UILabel alloc] init];
label.tag = tag;
[cell.contentView addSubview:label];
}
label.text = [NSString stringWithFormat:@"%zd", indexPath.item];
[label sizeToFit];
return cell;
}
- 在CYWaterFlowLayout.m文件中
#import "CYWaterFlowLayout.h"
@interface CYWaterFlowLayout()
@end
@implementation CYWaterFlowLayout
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
[super prepareLayout];
// 开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 设置布局属性的frame
attrs.frame = CGRectMake(arc4random(300), arc4random(300), arc4random(300), arc4random(300));
[self.attrsArray addObject:attrs];
}
}
/**
* 决定cell的排布
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
- 这里创建布局属性的时候之所以不在layoutAttributesForElementsInRect方法中创建,而在prepareLayout方法中去创建,是因为,如果你是继承自CollectionViewLayout,每次滑动屏幕的时候,layoutAttributesForElementsInRect都会调用,它的调用是动一下调用一次,默认是N频繁(如果你是继承自流水布局CollectionViewFlowLayout不是这种情况,流水布局内部默认有些控制你不能频繁调用它,除非重写shouldInvalidateLayoutForBoundsChange:方法才会频繁调用,这里是有区别的)
- 而我们的瀑布流算好一次就可以了,因为你算好了一次,再滚回来滚回去都是一样的。而prepareLayout只调用一次,所以我们放在这里去算(所以在这里先把布局属性用个数组装着,再放到layoutAttributesForElementsInRect里面去)。现在虽然滚来滚去layoutAttributesForElementsInRect它还是频繁的调用,但是它返回的是同一个东西,所以我们只算了一次。所以它没变,这样的话就优化了很多。
- 但是这里还是不够严谨,因为每一次当我们刷新的时候,prepareLayout都会重新调一次,这样的话,你把数组又重新加载进去了,这个数组就越来越大,而且一刷新,到时候,图片又是新的,高度又可能不太一样,那么又得从头到尾再算一遍,既然这样,以前的东西就不要留着了,所以得做一件什么事情?清除--清空以前的数组,清除之前所有的布局属性,不然你的布局属性会越来越大,好一些乱七八糟的东西进去。
- 而layoutAttributesForItemAtIndexPath是返回indexPath对应cell的布局属性,所以这里得实现以下
#import "CYWaterFlowLayout.h"
@interface CYWaterFlowLayout()
@end
@implementation CYWaterFlowLayout
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
[super prepareLayout];
// 清除之前所有的布局属性
[self.attrsArray removeAllObjects];
// 开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
/**
* 决定cell的排布
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 设置布局属性的frame
attrs.frame = CGRectMake(arc4random(300), arc4random(300), arc4random(300), arc4random(300));
return attrs;
}
- 现在瀑布流的基本骨架就搭建好了,别的就不必要关注了,你只要关注layoutAttributesForItemAtIndexPath方法中怎么去设置好布局属性的frame。因为瀑布流的核心就是排布方式不一样,你今后就只要去修改它里面的东西就行了
- 所以写自定义布局或者是写瀑布流最核心的东西还是要回到layoutAttributesForItemAtIndexPath方法,因为你继承自CollectionViewLayout,还是要实现这一些方法,并且最终回到这里做一些最核心事情
- 核心计算
- 这里就重点关注layoutAttributesForItemAtIndexPath方法里面的frame对应的X,Y,width,height怎么算了
- 做好瀑布流我们得知道一下几点:每一个对应cell宽度高度和位置,以及距离屏幕左右间距和上间距,以及每一行间距,每一列间距。
- 为了方便我们把这些值拿出来,我们先把间距都设置成固定的10,如果有特殊的需求定制,到时候再去修改一下就可以了
- 而且给一个默认的列数
/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};
- 关于这里的边缘间距上下左右的间距要写的话得写四个,变量常量就有点儿多了,所以想到了UIEdgeInsets
static const UIEdgeInsets CYDefaultEdgeInsets = (10, 10, 10, 10);
+ 而UIEdgeInsets是一个结构体,我们如果按平时像这么写,这就是一个函数调用了,虽然它只是类名函数。你如果调函数,意味着你在程序运行过程中才会执行,而像我们这种静态变量是�编译器确定的,也就是说编译器编译的时候才会确认这个固定的值,这样就不能执行这个代码了。函数是运行时调用,这样就不行
+ 这样的话我们用土方法用一个{}解决,因为它是结构体,相当于把10,10,10,10分别赋值给四个属性,所以今后要写静态变量的结构体,这也是一种方法
- 宽度好算,现在要开始去确定每一个cell的X和Y值,以及它们的高度,目前高度是无法确定的就先写死,先重点算一下cell的X和Y值
- 算X值就�求出列号就可以了,因为每一列的X值是相同的,所以重点你是找出最短的哪一列,所有就可以把Y值求出来了
// collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
// 设置布局属性的frame
CGFloat w = (collectionViewW - CYDefaultEdgeInsets.left - CYDefaultEdgeInsets.right - (CYDefaultColumnCount - 1) * CYDefaultColumnMargin) / CYDefaultColumnCount;
CGFloat h = 50 + arc4random_uniform(100);
- 但是每一列的高度都在变化,而且列数你也不确定,为了扩展性,所以也要搞个数组存起来,第0列最大的Y值,第1列最大的Y值,第2列最大的Y值,找出最大Y值最小的一列,为了好理解,�这里先存放所有列的当前高度
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
@end
@implementation CYWaterFlowLayout
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
- 找出高度最小的一列,所以要通过遍历,遍历之后就能找到最短的一列。遍历可以通过block或者for循环
- 先用block遍历
- 问题是数组里面只能放对象,所以block里面遍历最后取出来的时候要包装为对象
// 找出高度最短的那一列
__block NSInteger destColumn = 0;
// 目标那一列
__block CGFloat minColumnHeight = MAXFLOAT;
// 先保证minColumnHeight是最大的才能保证第一个家伙比他小
// block要改外面的变量,要加一个修饰符__block
[self.columnHeights enumerateObjectsUsingBlock:^(NSNumber *columnHeightNumber, NSUInteger idx, BOOL *stop) {
CGFloat columnHeight = columnHeightNumber.doubleValue;
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = idx;
// 目标列
}
}];
- for循环遍历
```objc
// 找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
// 第0个值我已经取出来了,我们就可以从第1个开始和0进行比较了
// 这里虽然是小小的优化少了一次遍历,但是不能小瞧少这一次的遍历,因为今后这个方法调得很频繁
// 这也是这里用for循环而不用block的原因,我们能控制从哪儿开始遍历
for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
- 我们已经找到了最短那列的高度了,所以X和Y值就可以确定了
```objc
CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
CGFloat y = minColumnHeight + CYDefaultRowMargin;
attrs.frame = CGRectMake(x, y, w, h);
- 但是还得做最后一件事情,你在这里加了一个新的列,所以我们还得更新一下高度
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
- 但是这里还有问题:0索引越界空数组,因为我们第一次访问那个数组,是一个空的数组,什么都没有。
- 所以我们在得在prepareLayout再做一次初始化:每次刷新,得把这个数组中存的高度都得删掉,从0开始再算一遍,所以prepareLayout得清除以前计算的高度,因为我们一刷新,可能里面的cell的高度都是不一样的,得重新算一遍,所以清除以前的高度,这是最严谨的做法
[super prepareLayout];
// 清除以前计算的所有高度
[self.columnHeights removeAllObjects];
for (NSInteger i = 0; i < CYDefaultColumnCount; i++) {
[self.columnHeights addObject:@(CYDefaultEdgeInsets.top)];
}
- 判断是不是第一行
CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
CGFloat y = minColumnHeight;
if (y != CYDefaultEdgeInsets.top) {
y += CYDefaultRowMargin;
}
attrs.frame = CGRectMake(x, y, w, h);
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
- 算出最长的那一列,去更新内容的高度
- (CGSize)collectionViewContentSize
{
CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (maxColumnHeight < columnHeight) {
maxColumnHeight = columnHeight;
}
}
return CGSizeMake(0, maxColumnHeight + CYDefaultEdgeInsets.bottom);
}
- 所以在CYWaterFlowLayout.m文件中
#import "CYWaterFlowLayout.h"
/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};
@interface CYWaterFlowLayout()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
@end
@implementation CYWaterFlowLayout
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
[super prepareLayout];
// 清除以前计算的所有高度
[self.columnHeights removeAllObjects];
for (NSInteger i = 0; i < CYDefaultColumnCount; i++) {
[self.columnHeights addObject:@(CYDefaultEdgeInsets.top)];
}
// 清除之前所有的布局属性
[self.attrsArray removeAllObjects];
// 开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
/**
* 决定cell的排布
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
// 设置布局属性的frame
CGFloat w = (collectionViewW - CYDefaultEdgeInsets.left - CYDefaultEdgeInsets.right - (CYDefaultColumnCount - 1) * CYDefaultColumnMargin) / CYDefaultColumnCount;
CGFloat h = 50 + arc4random_uniform(100);
// 找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
CGFloat y = minColumnHeight;
if (y != CYDefaultEdgeInsets.top) {
y += CYDefaultRowMargin;
}
attrs.frame = CGRectMake(x, y, w, h);
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
return attrs;
}
- (CGSize)collectionViewContentSize
{
CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (maxColumnHeight < columnHeight) {
maxColumnHeight = columnHeight;
}
}
return CGSizeMake(0, maxColumnHeight + CYDefaultEdgeInsets.bottom);
}
@end
- 这个瀑布流就基本实现了,特殊的需求或者是定制的内容,你去修改那些间距或者行数就行了
- �严谨处理:记得清除以前的数据,因为假如你做了一个下拉刷新,请求数据,一刷新就会重新调用prepareLayout,你的那些高度就会变了,不清除以前的数据,数组就会越来越大,就会出错,这也是瀑布流的核心。最关键最重要的的当然就是去找最短的那一列了
- 当然这里的高度还是得由外界决定,也就是服务器返回的数据决定:比如说你的功能要展示的是衣服,就应该由衣服的高度决定,展示的是新闻,就应该由新闻的高度决定。总之,你的高度是由数据决定的。
- 显示数据
- 我们上面做的是有缺陷的,我们图片的显示应该有完美的宽高比,它的宽度高度是应该由图片决定的,高度应该释放出来,而不是由你自己决定,应该由商品决定
- 商品模型数据先弄出来,这个很简单, 主要核心在接口设计
- plist文件中有商品模型数据,用模型去接收一下
- 在CYShop.h文件中存放模型数据
#import <UIKit/UIKit.h>
@interface CYShop : NSObject
@property (nonatomic, assign) CGFloat w;
@property (nonatomic, assign) CGFloat h;
@property (nonatomic, copy) NSString *img;
@property (nonatomic, copy) NSString *price;
@end
- 在CYShop.m文件中
#import "CYShop.h"
@implementation CYShop
@end
- 在CYShopCell.h文件中
#import <UIKit/UIKit.h>
@class CYShop;
@interface CYShopCell : UICollectionViewCell
@property (nonatomic, strong) CYShop *shop;
@end
- 在CYShopCell.m文件中
#import "CYShopCell.h"
#import "CYShop.h"
#import "UIImageView+WebCache.h"
@interface CYShopCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
@end
@implementation CYShopCell
- (void)setShop:(CYShop *)shop
{
_shop = shop;
// 1.图片
[self.imageView sd_setImageWithURL:[NSURL URLWithString:shop.img] placeholderImage:[UIImage imageNamed:@"loading"]];
// 2.价格
self.priceLabel.text = shop.price;
}
@end
-
CYShopCell.xib
- 在控制器中加载数据
- 因为要不断往后面拼接数据,所以搞一个可变数组数组
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;
@property (nonatomic, weak) UICollectionView *collectionView;
- 加载新商品数据从plist文件中加载
- 整个加载数据:具体步骤,我给了注释
- 在viewController.m文件中
#import "ViewController.h"
#import "CYWaterFlowLayout.h"
#import "CYShop.h"
#import "MJExtension.h"
#import "MJRefresh.h"
#import "CYShopCell.h"
@interface ViewController () <UICollectionViewDataSource>
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;
@property (nonatomic, weak) UICollectionView *collectionView;
@end
@implementation ViewController
- (NSMutableArray *)shops
{
if (!_shops) {
_shops = [NSMutableArray array];
}
return _shops;
}
static NSString * const CYShopId = @"shop";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupLayout];
[self setupRefresh];
}
- (void)setupRefresh
{
// 下拉刷新
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)];
// 一进入就刷新数据
[self.collectionView.mj_header beginRefreshing];
// 上拉自动刷新
self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)];
// 结束刷新
self.collectionView.mj_footer.hidden = YES;
}
// 下拉刷新数据
- (void)loadNewShops
{
// 模拟网络请求延时加载
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 字典转模型,从plist文件中加载商品数据模型数组
NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
// 每次刷新都要重新加载数据,所以先移除,再重新加载数据
[self.shops removeAllObjects];
[self.shops addObjectsFromArray:shops];
// 刷新数据
[self.collectionView reloadData];
// 数据回来后结束刷新
[self.collectionView.mj_footer endRefreshing];
});
}
// 上拉刷新数据
- (void)loadMoreShops
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
[self.shops addObjectsFromArray:shops];
// 刷新数据
[self.collectionView reloadData];
[self.collectionView.mj_footer endRefreshing];
});
}
- (void)setupLayout
{
// 创建布局
CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];
// 创建CollectionView
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
collectionView.backgroundColor = [UIColor whiteColor];
collectionView.dataSource = self;
[self.view addSubview:collectionView];
// 注册(从Xib加载)
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYShopCell class]) bundle:nil] forCellWithReuseIdentifier:CYShopId];
self.collectionView = collectionView;
}
#pragma mark - <UICollectionViewDataSource>
- (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
{
CYShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];
cell.shop = self.shops[indexPath.item];
return cell;
}
@end
- 现在基本格局搭建好了,接下来就是由我们模型的宽度高度去决定每个cell的宽度高度
- 完善接口
- �高度取决我们的商品,和商品有关,意味着我们得知道对应cell位置模型的宽高比才能算出高度来
- 我这里要做的是一个小框架,今后还可以用来展示别的内容,所以不能严重依赖项目模型,要有扩展性,那我要怎么做呢?
-
思想:
- 根据tableView的原理,tableView怎么做的,tableView的cell的高度由什么决定的?代理。为什么tableView要设计为代理决定呢?tableView保证了什么数据都能显示,�为了保证这一点,那么就由显示数据的那一方来告诉它你的高度是多少。那么我的瀑布流也想什么数据都能显示,就得由显示数据那一方决定,比如说控制器作为我代理来告诉我我的高度是多少。
- 我平时写代码的时候会想一想苹果为什么这么设计,为什么要搞数据源为什么要搞代理呢?它肯定是有道理的,你也会发现tableView是很牛逼的,它什么数据都能显示(设置界面,通讯录,淘宝等等任何一个应用都有它的身影)同一个控键,任何应用都会有它,这说明它是通用的,它为什么能通用呢?它里面展示内容的cell是由数据源告诉它的,它的数据都是由别人去告诉它的,所以换任何人的任何数据都能显示。所以我这里设计瀑布流也是这样,应该由别人来告诉我怎么做,这样才可以丰富多彩。不管是谁告诉我都可以,当然有个条件你得遵守我的协议,按照内容方式告诉我就行了。比如说你成为tableView的数据源,实现我那个数据源方法,在那个方法里返回东西给我,我就帮你显示出来,究竟显示什么我不管,总之你返回给我就对了
- 接下来弄个代理出来
- 类型先声明一下
@class CYWaterFlowLayout;
+ 代理是弱引用,用weak,加一个id,协议以自己的名字开头
```objc
@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
+ 这个方法里不再用optional了,optional用的时候那些方法是可有可无的,假如你监听我内部实现,可以监听也可以不监听。所以这里我用 required,得强制实现,因为你不告诉我高度,我没法显示,现在的级别不一样了,你作为代理,你必须告诉我,不告诉我,我就不帮你显示了,我直接报错。
+ 而且这个代理方法必须要有个返回值,,返回每个cell对应的高度
+ 这里的代理方法也是参考官方tableView代理去写的
```objc
@required
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
// 将我item的宽度传给代理,代理通过cell宽度就可以通过图片的宽高比去算出cell的高度返回给我
+ 让控制器成为我的代理
```objc
@interface ViewController () <UICollectionViewDataSource, CYWaterFlowLayoutDelegate>
layout.delegate = self;
- 在CYWaterFlowLayout.h文件中
#import <UIKit/UIKit.h>
@class CYWaterFlowLayout;
@protocol CYWaterFlowLayoutDelegate <NSObject>
@required
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
@end
@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
@end
- 在viewController.m文件中实现代理方法
#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
CYShop *shop = self.shops[index];
return itemWidth * shop.h / shop.w;
}
- 这段和模型相关的代码就放在控制器了,这样就和我们的瀑布流小框架没有一点关系了
- 我管你是谁显示的是商品,图片还是什么,我只向你索要高度,究竟显示什么,我不管,只要遵守协议,给我数据就可以了
// 设置布局属性的frame
CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];
- 现在就做到了通用性了。这个瀑布流现在和商品一点关系都没有了。你就看这个文件也看不出来它今后显示的是什么东西。你不知道它以后显示的是商品还是新闻还是大照片之类的东西。现在只要别人返回一个高度给我,不用管别人是啥,是新闻,图片或者是什么也好,框架里的东西不用改。
- 但是这里�还是设计得不够好,扩展性还不够强,因为那些列数,间距不应该写死,也是应该通过一个代理方法去控制的。它应该交给别人去设置,就算不设置,我也要给个默认值。
- 所以还提供一些代理方法,但是这次用optional,不用required。因为就算你不设置,我还有默认值。但是你要调用optional的时候,要判断
- 参考tableView
@optional
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)columnMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- 这样的话,优先从代理获取,代理没有就用默认的
// 声明一下,点语法就能调用了
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;
@end
@implementation CYWaterFlowLayout
#pragma mark - 常见数据处理
- (CGFloat)rowMargin
{
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)]) {
return [self.delegate rowMarginInWaterflowLayout:self];
} else {
return CYDefaultRowMargin;
}
}
- (CGFloat)columnMargin
{
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)]) {
return [self.delegate columnMarginInWaterflowLayout:self];
} else {
return CYDefaultColumnMargin;
}
}
- (NSInteger)columnCount
{
if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)]) {
return [self.delegate columnCountInWaterflowLayout:self];
} else {
return CYDefaultColumnCount;
}
}
- (UIEdgeInsets)edgeInsets
{
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)]) {
return [self.delegate edgeInsetsInWaterflowLayout:self];
} else {
return CYDefaultEdgeInsets;
}
}
- 现在,你完全可以自愿的选择性的去实现它告诉我,也就是说来到我们控制器,你作为我瀑布流的代理,你是有权利和资格决定我瀑布流控件里面是怎么一回事的,比如说里面的高度
#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterflowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
CYShop *shop = self.shops[index];
return itemWidth * shop.h / shop.w;
}
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
return 20;
}
- (CGFloat)columnCountInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
if (self.shops.count <= 50) return 2;
return 3;
}
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
return UIEdgeInsetsMake(10, 20, 30, 100);
}
- 现在就可以做到根本不用动瀑布流框架的源代码,在外面就可以�控制到今后怎么显示,也就是说我只要遵守你的协议,实现你的方法,就可以告诉你你里面应该怎么做,比如说:你可以就显示2列,让edgeInsetsInWaterflowLayout的间距也改变成自己自定义的值
- 其实可以认为这是一个纯正的MVC,比如说你就把它当做一个控件,你这个控件里面怎么排布,你的控件里面有多少列,完全是由控制器决定的,只要外面动一下,你里面就跟着改。像tableView就是一个完全的MVC,tableView里面显示什么是由外界决定的,外面的模型改了,外面的数据改了,它里面跟着改
- 假如外面显示数据比较多,比如说1000条的时候,意味着我们要for循环1000次,把所有东西给算完。这个数组就可能要装一千个对象,先从内存的角度,这个是无关紧要的。因为当我们在做移动开发的时候,是什么占内存呢?
- 占内存最关键的是下载的图片,音频和视频。而数组中装的attrs只是一个普通的对象,可能只有几B,我们创建1000个对象,也就类似于创建了一千个NSObject,只是一个普通的对象,所以从内存的角度,没什么担忧,唯一的担忧就可能是数据的问题,比如说要遍历一千次,就可能会导致线程会有那么一段时间稍微的阻塞,当然这个东西不算太耗时
- 有一些操作是比桀骜耗时的:比如说大家常见的字符串拼接,你要不断的拼接,拼接意味着你要产生一个新的字符串。还有NSLog,也是比较耗时的,也相当于每次都拼接出一个新的字符串出来
- 如果大家还是觉得耗时的话可以把它放在一个子线程中去算,然后再搞回来。或者你也可以去将它滚到什么位置就�只算那个范围里面的内容,但是你还是要把前面的先算出来。反正其实在这里,还是不算耗时的,大家不用担心,而且像我们的瀑布流数据也不会那么夸张,成千上万条,一般你拖到一定程度也会停住,或者说可能会刷新
- 这个瀑布流小框架对大家还是有好处的,也考虑到了很多东西,你可以直接把它拽走,用到你可能需要的项目中。只要你成为我的代理,实现这些方法,就可以告诉我高度多少,间距多少,里面的数据取决于你的cell,和CollectionView有关。你究竟想要在cell里面显示什么东西和我的瀑布流是没有关系的,我这里做的是自定义布局,只和布局有关,也就意味着你的内容和我一点关系都没有,可以放心大胆的用到项目中去
- 已经上传到github:****little-waterfall-framework-
- 对你有帮助,可以给颗星※
- 补充一点,可能会有朋友会认为那些高度,间距可以直接通过用属性来传值,更加简单。这种方式灵活性不是很强,有些麻烦,还得实现一个方法,还得返回东西。
- 那样是可以,但是你在使用的时候,可能就会在一个地方�传值比如:rowMargin = 10,在另外一个地方又传了一个rowMargin = 30。意味着你内部的东西又得重新算一遍。如果提供属性传值的接口去用,就可以乱改了
- 但是这里提供方法就不一样了,调用时刻是你决定的,这些方法是你决定什么时候调就什么时候调用,还可以做一些定制化的内容:
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
return 20;
}
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
if (self.shops.count <= 50) return 2;
return 3;
// 当现实内容少于50个的时候为两列,大于50个的时候为3列
}
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
return UIEdgeInsetsMake(10, 20, 30, 100);
}
- 最后
- 在CYWaterFlowLayout.h文件中
#import <UIKit/UIKit.h>
@class CYWaterFlowLayout;
@protocol CYWaterFlowLayoutDelegate <NSObject>
@required
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
@optional
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)columnMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
@end
@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
@end
- 在CYWaterFlowLayout.m文件中
#import "CYWaterFlowLayout.h"
/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};
@interface CYWaterFlowLayout()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** 内容的高度 */
@property (nonatomic, assign) CGFloat contentHeight;
// 声明一下,点语法就能调用了
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;
@end
@implementation CYWaterFlowLayout
#pragma mark - 常见数据处理
- (CGFloat)rowMargin
{
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)]) {
return [self.delegate rowMarginInWaterflowLayout:self];
} else {
return CYDefaultRowMargin;
}
}
- (CGFloat)columnMargin
{
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)]) {
return [self.delegate columnMarginInWaterflowLayout:self];
} else {
return CYDefaultColumnMargin;
}
}
- (NSInteger)columnCount
{
if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)]) {
return [self.delegate columnCountInWaterflowLayout:self];
} else {
return CYDefaultColumnCount;
}
}
- (UIEdgeInsets)edgeInsets
{
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)]) {
return [self.delegate edgeInsetsInWaterflowLayout:self];
} else {
return CYDefaultEdgeInsets;
}
}
#pragma mark - 懒加载
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
[super prepareLayout];
self.contentHeight = 0;
// 清除以前计算的所有高度
[self.columnHeights removeAllObjects];
for (NSInteger i = 0; i < self.columnCount; i++) {
[self.columnHeights addObject:@(self.edgeInsets.top)];
}
// 清除之前所有的布局属性
[self.attrsArray removeAllObjects];
// 开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
/**
* 决定cell的排布
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
// 设置布局属性的frame
CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];
// 找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < self.columnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
CGFloat y = minColumnHeight;
if (y != self.edgeInsets.top) {
y += self.rowMargin;
}
attrs.frame = CGRectMake(x, y, w, h);
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
// 记录内容的高度
CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
if (self.contentHeight < columnHeight) {
self.contentHeight = columnHeight;
}
return attrs;
}
- (CGSize)collectionViewContentSize
{
// CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
// for (NSInteger i = 1; i < self.columnCount; i++) {
// // 取得第i列的高度
// CGFloat columnHeight = [self.columnHeights[i] doubleValue];
//
// if (maxColumnHeight < columnHeight) {
// maxColumnHeight = columnHeight;
// }
// }
return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}
@end
- 在viewController.m文件中
#import "ViewController.h"
#import "CYWaterFlowLayout.h"
#import "CYShop.h"
#import "MJExtension.h"
#import "MJRefresh.h"
#import "CYShopCell.h"
@interface ViewController () <UICollectionViewDataSource, CYWaterFlowLayoutDelegate>
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;
@property (nonatomic, weak) UICollectionView *collectionView;
@end
@implementation ViewController
- (NSMutableArray *)shops
{
if (!_shops) {
_shops = [NSMutableArray array];
}
return _shops;
}
static NSString * const CYShopId = @"shop";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupLayout];
[self setupRefresh];
}
- (void)setupRefresh
{
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)];
[self.collectionView.mj_header beginRefreshing];
self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)];
self.collectionView.mj_footer.hidden = YES;
}
- (void)loadNewShops
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
[self.shops removeAllObjects];
[self.shops addObjectsFromArray:shops];
// 刷新数据
[self.collectionView reloadData];
[self.collectionView.mj_footer endRefreshing];
});
}
- (void)loadMoreShops
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
[self.shops addObjectsFromArray:shops];
// 刷新数据
[self.collectionView reloadData];
[self.collectionView.mj_footer endRefreshing];
});
}
- (void)setupLayout
{
// 创建布局
CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];
layout.delegate = self;
// 创建CollectionView
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
collectionView.backgroundColor = [UIColor whiteColor];
collectionView.dataSource = self;
[self.view addSubview:collectionView];
// 注册
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYShopCell class]) bundle:nil] forCellWithReuseIdentifier:CYShopId];
self.collectionView = collectionView;
}
#pragma mark - <UICollectionViewDataSource>
- (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
{
CYShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];
cell.shop = self.shops[indexPath.item];
return cell;
}
#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
CYShop *shop = self.shops[index];
return itemWidth * shop.h / shop.w;
}
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
return 20;
}
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
if (self.shops.count <= 50) return 2;
return 3;
}
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
return UIEdgeInsetsMake(10, 10, 10, 10);
}
@end
网友评论
这样不太合适吧..
首先contentHeight肯定是这三列最高的高度对吧
你直接吧刚更新的那一列高度给contentHeight 就是说明 刚跟新的就是最高的?
其实不一定啊,现在我这 三列中第二列最短 ,我下一个cell是往第二列上更新 更新一个很矮的 那么还是这一列最短啊.
所以 少不了的循环 遍历找到最高的列吧.
random(300)报错呢,