瀑布流有幾種實(shí)現(xiàn)方式,這里只說我寫的這一種
首先瀑布流直觀可以看出是塊分布,不難想到用 UICollectionView 去實(shí)現(xiàn)。
其次就是需要我們自定義一個類繼承 UICollectionViewFlowLayout 來計(jì)算瀑布流的顯示方式。
在寫代碼以前,要想好瀑布流到底是什么原理排列的
在寫代碼以前,要想好瀑布流到底是什么原理排列的
在寫代碼以前,要想好瀑布流到底是什么原理排列的
重要的事情說三遍: )
其實(shí)總結(jié)出來就是一個問題:
每次Add一個Object的時候,要加在哪一列
沒錯,就是加在上一排最短的那一列
那么我們要怎么知道每個item高度呢?
因?yàn)槊總€使用這個Layout的人的需求都不一樣,我們不能要求每個item都只是一個圖片(這里用圖片作為例子的原因是image.size)
所以我們需要Coder在使用這個Layout的時候告訴我們這個item的高度
//這里類似UITableView的方法
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
而且這個高度是強(qiáng)制性要獲取的,so。。。(@required你懂得)
UICollectionViewWaterfallLayout.h
#import <UIKit/UIKit.h>
@class UICollectionViewWaterfallLayout;
@protocol UICollectionViewWaterfallLayoutDelegate <NSObject>
@required
- (CGFloat)waterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout heightForRowAtIndexPath:(NSInteger)index itemWidth:(CGFloat)itemWidth;
@optional
//列數(shù)
- (int)columnCountInWaterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout;
//每列間距(不算邊距)
- (CGFloat)columnMarginInWaterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout;
//行間距(不算邊距)
- (CGFloat)rowMarginInWaterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout;
//邊距
- (UIEdgeInsets)edgeInsetsInWaterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout;
@end
@interface UICollectionViewWaterfallLayout : UICollectionViewLayout
@property (nonatomic ,weak) id<UICollectionViewWaterfallLayoutDelegate> delegate;
@end
同時我也定義了4個Optional方法,Coder有需求就對號入座去實(shí)現(xiàn)就好了
UICollectionViewWaterfallLayout.m
既然有4個可修改項(xiàng),那么我們就需要一些Default屬性(不了解UICollectionView有哪些可以改的東西,例如邊距,可以自己Com+點(diǎn)進(jìn)去自己看看,這里就不多做解釋了)
//默認(rèn)的列數(shù)
static const NSInteger DefaultColumnCount = 3;
//每一列之間的間距
static const CGFloat DefaultColumnMargin = 10;
//沒一行之間的間距
static const CGFloat DefaultRowMargin = 10;
//邊緣間距
static const UIEdgeInsets DefaultEdgeInsets = {10, 10, 10, 10};
- (CGFloat)rowMargin
{
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterfallLayout:)]) {
return [self.delegate rowMarginInWaterfallLayout:self];
} else {
return DefaultRowMargin;
}
}
- (CGFloat)columnMargin
{
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterfallLayout:)]) {
return [self.delegate columnMarginInWaterfallLayout:self];
} else {
return DefaultColumnMargin;
}
}
- (NSInteger)columnCount
{
if ([self.delegate respondsToSelector:@selector(columnCountInWaterfallLayout:)]) {
return [self.delegate columnCountInWaterfallLayout:self];
} else {
return DefaultColumnCount;
}
}
- (UIEdgeInsets)edgeInsets
{
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterfallLayout:)]) {
return [self.delegate edgeInsetsInWaterfallLayout:self];
} else {
return DefaultEdgeInsets;
}
}
前面已經(jīng)說了如何布局的思想
這里需要創(chuàng)建3個屬性來實(shí)現(xiàn)我們的想法
//存放所有cell的布局屬性
@property (nonatomic, strong) NSMutableArray *attrsArray;
//存放所有列的當(dāng)前高度
@property (nonatomic, strong) NSMutableArray *columnHeights;
//內(nèi)容的高度
@property (nonatomic, assign) CGFloat contentHeight;
重寫其中兩個簡單的方法 我就不多做解釋了 注釋已經(jīng)寫的很詳細(xì)了
//方法是返回UICollectionView的可滾動范圍
- (CGSize)collectionViewContentSize {
return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}
//方法返回的是一個裝著UICollectionViewLayoutAttributes的數(shù)組
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return self.attrsArray;
}
劃重點(diǎn)了?。。?/strong>
重寫核心方法
- (void)prepareLayout;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
首先reload的時候不難發(fā)現(xiàn)會走- (void)prepareLayout;方法,所以每次我們都要清空之前的計(jì)算記錄再重新計(jì)算
注:這個方法必須調(diào)用一下父類的實(shí)現(xiàn),不然你可以試試會怎么樣??
[super prepareLayout];
//清除之前計(jì)算的所有高度,因?yàn)樗⑿碌臅r候回調(diào)用這個方法
self.contentHeight = 0;
[self.columnHeights removeAllObjects];
[self.attrsArray removeAllObjects];
第一件事,我們是要確定瀑布流的列數(shù)
int forCount = [self.delegate respondsToSelector:@selector(columnCountInWaterfallLayout:)] ? [self.delegate columnCountInWaterfallLayout:self] : DefaultColumnCount;
然后向我們的數(shù)組中加入初始值(這里不加會崩潰)
for (NSInteger i = 0; i < forCount; i++) {
[self.columnHeights addObject:@(self.edgeInsets.top)];
}
開始創(chuàng)建每一個cell對應(yīng)的布局屬性
for (int section = 0; section < self.collectionView.numberOfSections; section++) {
for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:section]; i++) {
// 創(chuàng)建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
// 獲取indexPath位置cell對應(yīng)的布局屬性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
這樣- (void)prepareLayout;就算是完成了,這里面調(diào)用了另一個需要重寫的方法- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath里面大部分都是計(jì)算和存儲高度的code,就不多做解釋了
//方法返回indexPath位置的UICollectionViewLayoutAttributes
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGFloat collectionViewW = self.collectionView.frame.size.width;
CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right -(self.columnCount - 1) * self.columnMargin) / self.columnCount;
CGFloat h = [self.delegate waterfallLayout:self heightForRowAtIndexPath:indexPath.item itemWidth:w];
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 0; i < self.columnCount; 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;
}
到此為止,這個類算是完成了
然后到Controller里
<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewWaterfallLayoutDelegate>
UICollectionViewWaterfallLayout *layout = [[UICollectionViewWaterfallLayout alloc] init];
layout.delegate = self;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
同UITableView一樣把該寫的都寫上
到了要實(shí)現(xiàn)那個必須實(shí)現(xiàn)的@required的方法的時候,也許真正用到項(xiàng)目里的Coder就會發(fā)現(xiàn)一個問題,大部分的情況下瀑布流是用來展示 圖片+Something,而圖片是網(wǎng)絡(luò)請求加載,異步獲取到圖片才能拿到寬高,這里我們項(xiàng)目也遇到過,也實(shí)現(xiàn)過這種需求,但體驗(yàn)不太好,不!是非常不好!
也許是因?yàn)槲覍τ趯慞laceHolderCell的理解不夠(個人覺得微博的效果就不錯,但我還不會??),經(jīng)過跟別的公司的小朋友討論+用戶體驗(yàn)的考慮下,90%的小朋友們的做法是后臺在返回圖片Url的同時給你圖片的W+H,這樣就能完美解決問題了,由于我們后臺云存儲獲取圖片的時候可在Url后控制返回圖片的大小,所以比較容易的實(shí)現(xiàn)了。
搞一個Model
@interface ImageModel : NSObject
//為了用戶體驗(yàn),異步加載網(wǎng)絡(luò)圖片,我們公司討論結(jié)果是后臺存儲好圖片寬高,與json數(shù)據(jù)一起返回,這樣用戶體驗(yàn)比較好
@property (nonatomic, readonly) CGFloat imageWidth;
@property (nonatomic, readonly) CGFloat imageHeight;
@property (nonatomic, strong) NSString *imageUrl;
@end
返回去實(shí)現(xiàn)協(xié)議方法
- (CGFloat)waterfallLayout:(UICollectionViewWaterfallLayout *)waterfallLayout heightForRowAtIndexPath:(NSInteger)index itemWidth:(CGFloat)itemWidth {
ImageModel *model = self.dataArray[index];
return itemWidth * model.imageHeight / model.imageWidth;
}
至此如果沒什么問題運(yùn)行應(yīng)該就能看到效果了

有問題可以留言??
有幫助可以??一下