瀑布流:
由很多的格子组成,但是每个格子的宽度和高速都是不确定的,是在动态改变的,就像瀑布一样,是一条线一条线的。说明:使用tableView不能实现瀑布流式的布局,因为tableView是以行为单位的,它要求每行(cell)的高度在内部是一致的。本文章介绍了如何自定义一个瀑布流控件来展示信息,本文介绍模仿UITableView的做法自定义瀑布流包括可重用机制的实现。
接口的实现:
接口的实现包括数据源代理和自身代理所需要用到的方法,我们模仿官方的UITableView可以写出自定义View的代理方法:
#import <UIKit/UIKit.h>
typedef enum {
JYHWaterfallFlowViewMarginTypeTop,
JYHWaterfallFlowViewMarginTypeBottom,
JYHWaterfallFlowViewMarginTypeLeft,
JYHWaterfallFlowViewMarginTypeRight,
JYHWaterfallFlowViewMarginTypeRow,
JYHWaterfallFlowViewMarginTypeColumn
}JYHWaterfallFlowViewMarginType;
@class JYHWaterfallFlowView, JYHWaterfallFlowCell;
/**
* 数据源代理协议
*/
@protocol JYHWaterfallFlowDataSource<NSObject>
@required
/**
* 该View共有多少个数据
*/
- (NSUInteger)numberOfCellsInWaterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView;
/**
* 返回对应位置的cell
*/
- (JYHWaterfallFlowCell *)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView cellAtIndex:(NSUInteger)index;
@optional
/**
* 该瀑布流要显示几列
*/
- (NSUInteger)numberOfColumnsInWaterfallFlowView:(JYHWaterfallFlowView *)waterflowFlowView;
@end
/**
* JYHWaterfallFlowView代理
*/
@protocol JYHWaterfallFlowDelegate <UIScrollViewDelegate>
@optional
/**
* 第index位置cell对应的高度
*/
- (CGFloat)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView heightAtIndex:(NSUInteger)index;
/**
* 选中第index位置的cell
*/
- (void)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView didSelectAtIndex:(NSUInteger)index;
/**
* 返回间距
*/
- (CGFloat)waterfallFlowView:(JYHWaterfallFlowView *)waterflowFlowView marginForType:(JYHWaterfallFlowViewMarginType)type;
@end
@interface JYHWaterfallFlowView : UIScrollView
/**
* 数据源代理
*/
@property (nonatomic, weak)id<JYHWaterfallFlowDataSource>dataSource;
/**
* JYHWaterfallFlowView代理
*/
@property (nonatomic, weak)id<JYHWaterfallFlowDelegate>waterfallFlowViewdalegate;
/**
* 刷新数据方法
*/
- (void)reloadData;
/**
* 根据标示符从缓存池中查找可重用的Cell
*
* @param identifier 标示符
*
* @return cell
*/
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end
这里需要注意dalegate的命名由于它的父类UIScrollView已经有个叫dalegate的属性了,这里我不小心调了好长时间。。
接下来实现reloadData方法,该方法主要是求出每个cell的Frame里面有一些小算法在里面:
- (void)reloadData {
//cell的总数
long numberOfcells = [self.dataSource numberOfCellsInWaterfallFlowView:self];
//View的总列数
long numberOfColumn = [self numberOfColumn];
//间距
CGFloat topM = [self marginForType:JYHWaterfallFlowViewMarginTypeTop];
CGFloat rightM = [self marginForType:JYHWaterfallFlowViewMarginTypeRight];
CGFloat bottomM = [self marginForType:JYHWaterfallFlowViewMarginTypeBottom];
CGFloat leftM = [self marginForType:JYHWaterfallFlowViewMarginTypeLeft];
CGFloat columnM = [self marginForType:JYHWaterfallFlowViewMarginTypeColumn];
CGFloat rowM = [self marginForType:JYHWaterfallFlowViewMarginTypeRow];
//cell的宽度
CGFloat cellW = (self.bounds.size.width - (rightM + leftM) - (numberOfColumn - 1) * columnM) / numberOfColumn;
// 用一个C语言数组存放所有列的最大Y值
CGFloat maxYOfColumns[numberOfColumn];
for (int i = 0; i<numberOfColumn; i++) {
maxYOfColumns[i] = 0.0;
}
for (int i = 0; i < numberOfcells; i++) {
//cell该放到那一列(最短的那一列),默认为第一列
NSUInteger cellColumn = 0;
//最短一列的最大Y值
CGFloat maxOfcellColumn = maxYOfColumns[cellColumn];
//求最短的一列和最短列的最大Y值
for (int j = 1; j < numberOfColumn; j++) {
if(maxYOfColumns[j] < maxOfcellColumn) {
cellColumn = j;
maxOfcellColumn = maxYOfColumns[j];
}
}
CGFloat cellH = [self hightOfIndex:i];
CGFloat cellX = leftM + cellColumn * (cellW + columnM);
CGFloat cellY = 0.0;
if (maxOfcellColumn == 0.0) {//首行
cellY = topM;
} else {
cellY = rowM + maxOfcellColumn;
}
CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);
[self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
//更新最短列的最大Y值
maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame);
}
//设置scrollView的ContentSize
CGFloat contentH = maxYOfColumns[0];
for (int j = 1; j<numberOfColumn; j++) {
if (maxYOfColumns[j] > contentH) {
contentH = maxYOfColumns[j];
}
}
contentH += bottomM;
self.contentSize = CGSizeMake(0, contentH);
}
接下来是重点了,重用cell,将不在屏幕上的cell放入到缓存池中,由于scrollView在滚动过程中会调用 layoutSubviews这个方法,所以我们可以在这个方法里面进行一些重用操作:
/**
* 当ScrollView滚动时会调用这个方法
*/
- (void)layoutSubviews {
[super layoutSubviews];
NSUInteger numberOfCells = [self.cellFrames count];
for (int i = 0; i < numberOfCells; i++) {
//取出cellFrame
CGRect cellFrame = [self.cellFrames[i] CGRectValue];
//优先取出字典中的cell
JYHWaterfallFlowCell *cell = self.displayingCells[@(i)];
if([self isInScreen:cellFrame]) {//在屏幕上出现
if(cell == nil) {
cell = [self.dataSource waterfallFlowView:self cellAtIndex:i];
cell.frame = cellFrame;
[self addSubview:cell];
//存到字典中
self.displayingCells[@(i)] = cell;
}
} else {
if (cell) {
[cell removeFromSuperview];
[self.displayingCells removeObjectForKey:@(i)];
//将cell放入到缓存池中
[self.reusableSet addObject:cell];
}
}
}
}
用访问标识从缓存池中取出cell:
-(id)dequeueReusableCellWithIdentifier:(NSString *)identifier {
__block JYHWaterfallFlowCell *resuableCell;
[self.reusableSet enumerateObjectsUsingBlock:^(JYHWaterfallFlowCell *cell, BOOL *stop) {
if([cell.identifier isEqualToString:identifier]) {
resuableCell = cell;
*stop = YES;
}
}];
if(resuableCell) {//用完后将cell从缓存池中移除,防止越积越多
[self.reusableSet removeObject:resuableCell];
}
return resuableCell;
}
记得在这个waterfallFlowView加入到父控件时像UITableView一样刷新数据:
- (void)willMoveToSuperview:(UIView *)newSuperview {
[self reloadData];
}
当然还有一些懒加载及一些小方法,在这里就不一一列出了,我把demo上传了,想完全了解的可以下载下来看看,本demo只有第一幅图的代码,第二幅图也就是自定义cell往cell里加东西而已。
Demo连接:http://download.csdn.net/detail/u013672551/9212271