在网上有现成的瀑布流实现方法,但我还是犯贱,自己写了一个WaterfallView。
![](https://img-my.csdn.net/uploads/201206/29/1340945613_8905.jpg)
总体思路:
瀑布流视图继承于UIScrollView,在其中创建一个普通的UIView对象waterView,然后再在waterView中创建瀑布流的各个显示图像的元素,即WaterfallCell的对象。
实现方法:
首先实现瀑布流元素类WaterfallCell,这个类可以写得很简单,其中包括一个UIImageView对象,用于显示图片即可。
比如:
WaterfallCell.h:
#import <UIKit/UIKit.h>
#import "ResListItem.h"
@interface WaterfallCell : UIControl
<ResPicLoaderDelegate>
{
UIImage* image;
UIImageView* imgView;
ResListItem* resItem;
}
- (id)initWithResItem:(ResListItem*)item;
其中ResListItem包括item信息,是自己写的,这里不作讲述。
WaterfallCell.m:
#import "WaterfallCell.h"
@implementation WaterfallCell
- (id)initWithResItem:(ResListItem*)item
{
self = [super initWithFrame:CGRectZero];
if (self)
{
imgView = [[UIImageView alloc]initWithFrame:CGRectZero];
[self addSubview:imgView];
resItem = item;
ResIconLoader* iconLoader = [resItem resIcon];
[iconLoader setLoadDelegate:self];
image =[iconLoader loadIconFromFile];
[imgView setImage:image];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect rtClient = [self bounds];
CGRect imgBound = CGRectMake(rtClient.origin.x + 1, rtClient.origin.y + 1, rtClient.size.width -2, rtClient.size.height - 2);
[imgView setFrame:imgBound];
}
- (void)resIconLoadEnded:(ResIconLoader *)iconLoader
{
UIImage* img = [iconLoader loadIconFromFile];
[imgView setImage:img];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
@end
WaterfallView需要知道有多少个元素需要显示。可以借鉴UITableView,通过协议,从UIViewController获取瀑布流元素的个数,并且通过协议,由UIViewController创建各个元素。因此定义瀑布流的协议:
@protocol WaterfallDelegate <NSObject>
@required
- (NSInteger) numberOfRows;
- (WaterfallCell*)cellForRow:(int)row;
@end
WaterfallView.h
#import <UIKit/UIKit.h>
#import "WaterfallCell.h"
@protocol WaterfallDelegate <NSObject>
@required
- (NSInteger) numberOfRows;
- (WaterfallCell*)cellForRow:(int)row;
@end
@interface WaterfallCellRect : NSObject
{
CGRect cellFrame;
}
@property CGRect cellFrame;
- (id) initWithRect:(CGRect)frame;
@end
@interface WaterfallView : UIScrollView
{
id<WaterfallDelegate> waterDelegate;
int cellCount;
UIView* waterView;
NSMutableArray* boundsArray;
float totalHeight;
}
@property id<WaterfallDelegate> waterDelegate;
@property (readonly) int cellCount;
- (id)initWithDelgate:(id<WaterfallDelegate>)waterfallDelegate Frame: (CGRect)frame;
- (void) reloadData;
- (CGRect) cellFrame:(int) nIndex;
- (float) cellPosY:(int)nIndex;
- (void)viewSize;
@end
除了得到各个元素以外,还需要知道UIScrollView的高度,以便控制Scroll。另外最重要的是需要知道各个元素的位置。
想了个办法解决这两个问题
在WaterfallView中用一个动态数组boundsArray,保存每个cell的Frame,还有一个成员变量totalHeight,用于保存UIScrollView的高度。 在获取了cell个数之后,初始化这个动态数组,同时可以计算除UIScrollView的高度。
创建各个Cell的Frame
根据cell的index,可以创建或则获取其Frame。如果index小于boundsArray的元素个数,说明这个cell的Frame已经创建,直接从boundsArray中获取Frame。反之,创建一个新的Frame,并添加到boundsArray。具体实现方法:boundsArray
- (float) cellPosY:(int)nIndex
{
CGRect clientBound = [self bounds];
float posY = clientBound.origin.y;
if(nIndex > 2)
{
WaterfallCellRect* upCellRect = [boundsArray objectAtIndex:nIndex - 3];
CGRect upRect = upCellRect.cellFrame;
posY = upRect.origin.y + upRect.size.height;
}
return posY;
}
- (CGRect) cellFrame:(int) nIndex
{
CGRect clientBound = [self bounds];
if(nIndex < [boundsArray count])
{
WaterfallCellRect* cellRect = [boundsArray objectAtIndex:nIndex];
CGRect bound = [cellRect cellFrame];
return bound;
}
int nColume = nIndex % 3;
float cellWidth = clientBound.size.width / 3;
int nWidth = cellWidth;
float nCellHeight = arc4random() % nWidth ;
NSLog(@"cell height = %f", nCellHeight);
if (nCellHeight < cellWidth * 2 / 3)
{
nCellHeight += cellWidth;
}
float cellPosY = [self cellPosY:nIndex];
CGRect bound = CGRectMake(clientBound.origin.x + nColume * cellWidth, cellPosY, cellWidth, nCellHeight);
WaterfallCellRect* cellRect = [[WaterfallCellRect alloc]initWithRect:bound];
[boundsArray addObject:cellRect];
//计算新添加的Frame的底部是否已经超过了totalHeight,如果超过了,重新设置totalHeight
if(totalHeight < bound.origin.y + bound.size.height)
{
totalHeight = bound.origin.y + bound.size.height;
}
return bound;
}
在获取了cell个数之后,就可以创建各个cell的Frame,并计算UIScrollView的高度了。具体实现方法:
-(void)viewSize
{
CGRect scrollViewBounds = [self bounds];
[boundsArray removeAllObjects ];
totalHeight = 0;
float oriHeight = scrollViewBounds.size.height;
for (int nIndex = 0; nIndex < cellCount; ++nIndex)
{
[self cellFrame:nIndex];
}
if(totalHeight < oriHeight)
{
totalHeight = oriHeight;
}
scrollViewBounds.size.height = totalHeight;
[self setContentSize:scrollViewBounds.size];
if(!waterView)
{
waterView = [[UIView alloc]initWithFrame:scrollViewBounds];
[self addSubview:waterView];
}
else
{
[waterView setFrame:scrollViewBounds];
}
}
然后,模仿UITableView,实现reLoadData方法,初始化各个cell即可。
- (void)reloadData
{
if(!waterDelegate)
return;
cellCount = [waterDelegate numberOfRows];
[self viewSize];
for (int nIdx = 0; nIdx < cellCount; ++nIdx)
{
WaterfallCell* cell = [waterDelegate cellForRow:nIdx];
CGRect cellFrame = [self cellFrame:nIdx];
[cell setFrame:cellFrame];
[waterView addSubview:cell];
}
}
最后在实现WaterfallView的初始化方法:
- (id)initWithDelgate:(id<WaterfallDelegate>)waterfallDelegate Frame: (CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
boundsArray = [[NSMutableArray alloc]init];
waterDelegate = waterfallDelegate;
cellCount = [waterDelegate numberOfRows];
[self viewSize];
[self reloadData];
}
return self;
}
终于大功告成,后续的工作,还可以实现各个cell的点击事件等等操作,就根据具体需求,具体实现。
回头看看,其实还是挺容易的,只要将思路