瀑布流框架的搭建

瀑布流大家都应该熟悉了,现在大部分电商应用中或多或少的都用到瀑布流,它可以吸引用户的眼球,使用户不易产生视觉疲劳,苹果在iOS6中增添了UICollectionView控件,这个控件可以说是UITableView的升级版,通过这个控件我们就能很简单的做出瀑布流,后面通过自己的封装可以让其变成一个小框架,更简单的应用到我们之后的开发中

如果想做瀑布流,那么就要自定义CollectionViewFlowLayout,因为这个类中有一个返回collectionView中所有子控件的布局属性(布局属性中有控件的frame和索引)的方法
<p><pre> - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attri in arr) {
attri.frame = CGRectMake(0, 0, 100, 300);
}
NSLog(@"%@", arr);
return arr;
}
</pre></p>

这个方法会将collectionView中的所有子控件的布局属性计算一次,计算之后就会被缓存起来,当已经计算过的cell,再次出现时也不会在重复去计算它的尺寸。
注意:如果要进行刷新数据那么要记得将之前的布局属性进行清空,不然会出现布局错误
<p><pre>
// 把用来装所有布局属性的数据做清空处理
[self.attrArrM removeAllObjects];
</pre></p>

现在定义一个可变数组属性来存储一会自己计算的布局属性中的frame,让上面的方法返回自己定义的布局属性
<p><pre>
// 用来保存所有布局属性的可变数组
@property (nonatomic, strong) NSMutableArray *attrArrM;
</pre></p>

那么我们在哪个方法中计算自己定义的布局属性呢?
有这么个方法
<p><pre> - (void)prepareLayout {
// 要调用父类的prepareLayout
[super prepareLayout];
}
</pre></p>

当collectionView中的所有子控件即将显示的时候就会来调用此方法做布局前的准备工作,准备itemSize...等等属性 同时当布局的属性发生变化时也会来调用此方法 当刷新数据之后也会来调用此方法重新做布局前的准备工作

在这个方法中可以通过collectionViewFlowLayout的collectionView的numberOfItemInSection这个方法获得一组中的所有cell
<p><pre>
// 获得一组中的所有cell
NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
</pre></p>

通过for循环(循环次数为一组中有多少个cell)创建布局属性,在循环中需要计算每一个cell的frame 所以后台要给真实的图片尺寸(如果不给,自己计算的尺寸会造成图片闪)
同时显示时一般都会等比例缩放。

cell宽度的计算

cell的宽 = (内容的宽 - (列数 - 1) * 最小间距) / 列数
内容的宽 = collectionView的宽 - 组的左边间距 - 右边间距

<p><pre>
CGFloat contentWidth = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right;
CGFloat cellW = (contentWidth - (self.columnCount - 1) * self.minimumInteritemSpacing) / self.columnCount;
</pre></p>

cell高度的获取

当要获得cell高的时候需要通过控制器来获得模型图片的高度(高度要和itemW有一定的比例要不图片会过大 height / width * itemW;),因此需要让控制器成为我们的代理,在自定义CollectionViewFlowLayout.h文件中定义协议如下:
<p><pre>

import <UIKit/UIKit.h>

@class ZHYCollectionViewFlowLayout;
@protocol ZHYCollectionViewFlowLayoutDelegate <NSObject>
@required

  • (CGFloat)waterFallFlowLayoutWithItemHeight:(ZHYCollectionViewFlowLayout *)flowLayout itemW:(CGFloat)itemW CellIndexPath:(NSIndexPath *)indexPath;
    @end
    </pre></p>

并且设置代理属性如下:
<p><pre> @property (weak, nonatomic) id<ZHYCollectionViewFlowLayoutDelegate> delegate
</pre></p>

在计算cell高的时候调用代理方法获得cell的高度
<p><pre>
CGFloat cellH = [self.delegate waterFallFlowLayoutWithItemHeight:self itemW:cellW CellIndexPath:indexPath];
</pre></p>

为什么要在代理方法中加入indexPath,因为要通过indexPath来获取模型

cellX的计算

在计算cellX的时候
如果通过 NSInteger col = i % self.columnCount;获取列号要注意一个问题:
这样每次都是按顺序排列图片的,那么如果图片的尺寸参差不齐有的特别短有的又特别长,不巧的是长的都在一列短的又都在一列这样会造成美观性会很差,那么怎么解决这个问题呢,我们能不能让每一行添加完毕后,下一行在添加的时候将第一个添加在上一行高度最短的那面图片下面呢?答案当然是可以的-_-!
这时候我们就要定义一个可变字典属性,来存储每一列的高度,用列号来当字典的key
<p><pre>
// 用来记录每一列的最的高度
@property (nonatomic, strong) NSMutableDictionary *colDict;
</pre></p>

在prepareLayout方法中给每一列的高度的字典一个默认的高度
<p><pre>
for (NSInteger i = 0; i<有几列; i++) {
NSString *str = [NSString stringWithFormat:@"%ld", i];
self.colDict[str] = @(self.sectionInset.top);
} </pre></p>

获得最矮的那一列列号的方法
<p><pre>

  • (NSString *)minCol {
    __block NSString *min = @"0";
    [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
    if ([obj floatValue] < [self.colDict[min] floatValue]) {
    min = key;
    }
    }];
    return min;
    }
    </pre></p>

因此列号为
<p><pre>
NSInteger col = [[self minCol] integerValue];
</pre></p>

cellX为:
<p><pre>
CGFloat cellX = self.sectionInset.left + (cellW + self.minimumInteritemSpacing) * col;
</pre></p>

cellY的计算

计算cellY
<p><pre>
// 用列号当字典的key
NSString *colStr = [NSString stringWithFormat:@"%ld", col];
CGFloat cellY = [self.colDict[colStr] floatValue];
// 累计每一列的高度
self.colDict[colStr] = @(cellY + cellH + self.minimumLineSpacing);
</pre></p>

这样我们就计算完了每一个cell的X,Y,W,H,我们来设置布局属性的frame
<p><pre>
// 设置属性的frame
attr.frame = CGRectMake(cellX, cellY, cellW, cellH);
</pre></p>

同时我们也给尾部视图添加一个布局属性,代码如下

<p><pre>
// 创建尾部视图的布局属性
// 创建footerview索引
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
// 必须是额外的layoutAttributesForSupplementaryViewOfKind
UICollectionViewLayoutAttributes *footerAttr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind: UICollectionElementKindSectionFooter withIndexPath:indexPath];
footerAttr.frame = CGRectMake(0, [self.colDict[self.maxCol] floatValue] - self.minimumLineSpacing, self.collectionView.bounds.size.width, 50);
[self.attrArrM addObject:footerAttr];
</pre></p>

这里要用到最高列,因为尾部视图要放在cell最下面,获得最高列索引的方法为:
<p><pre>
// 用来取出最高那一列的列号

  • (NSString *)maxCol {
    __block NSString *maxCol = @"0";
    [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
    if ([obj floatValue] > [self.colDict[maxCol] floatValue]) {
    maxCol = key;
    }
    }];
    return maxCol;
    }
    </pre></p>

最后我们通过上面说过的方法把布局属性返回
<p><pre>

  • (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self.attrArrM;
    }
    </pre></p>

自定义布局属性的时候还要注意返回真实的contentSize,代码如下:

<p><pre>

  • (CGSize)collectionViewContentSize {
    return CGSizeMake(0, [self.colDict[self.maxCol] floatValue] + self.footerReferenceSize.height - self.minimumLineSpacing);
    }
    </pre></p>

这时基本已经完成了,但如果我想要把这个瀑布流布局做成一个简单的框架就需要在简单的实现些初始化方法
在CollectionViewFlowLayout.h还要定义一个一行有几个cell的属性,当控制器引用这个类之后可以自行设置
<p><pre>
@property (assign, nonatomic) NSInteger columnCount;
</pre></p>

提供一些初始化方法,使其默认为一行有3个cell, cell间距及行间距为10,内边距为顶部20, footerReferenceSize, headerReferenceSize都为50,50
<p><pre>

  • (instancetype)init
    {
    self = [super init];
    if (self) {
    self.columnCount = 3;
    self.minimumInteritemSpacing = 10;
    self.minimumLineSpacing = 10;
    self.footerReferenceSize = CGSizeMake(50, 50);
    self.headerReferenceSize = CGSizeMake(50, 50);
    self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
    }
    return self;
    }
    </pre></p>
    <p><pre>
  • (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super initWithCoder:aDecoder];
    if (self) {
    self.columnCount = 3;
    self.minimumInteritemSpacing = 10;
    self.minimumLineSpacing = 10;
    self.footerReferenceSize = CGSizeMake(50, 50);
    self.headerReferenceSize = CGSizeMake(50, 50);
    self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
    }
    return self;
    }
    </pre></p>

这样我们简单的瀑布流框架就搭建成功了~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,646评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,979评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,391评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,356评论 0 215
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,740评论 3 293
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,836评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,022评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,764评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,487评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,728评论 2 252
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,200评论 1 263
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,548评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,217评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,134评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,921评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,919评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,766评论 2 274

推荐阅读更多精彩内容