上篇说到瀑布流,我是用的UITableView来实现的,因为在这样同列的图片UITableView有天然的优势,主要是计算图片的位置非常方便,同时能重用减少了不少的工作量。2个月前我做了一个类似于美丽说的产品,其中主要的逻辑就是来做一个瀑布流。
思路很自然,就是模仿UItableView内存重用的机制。
1。首先设计你的类,这个类提供的接口就是你的图片的地址的集合,毕竟瀑布流很少去读本地的数据,通常是异步去网络请求图片数据,
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "UIImageView+WebCache.h"
@class QuYouAppWaterFlowCell;
@interface QuYouAppWaterfallView : UIScrollView<UIScrollViewDelegate>{
float y1;
float y2;
float y3;
float y4;
UIViewController *targetVC; //代理的目标target
SEL action; //tagert 执行的方法
NSMutableArray *imageArray;
NSMutableArray *reuseQueue;
NSMutableDictionary *_dicReuseCells; //重用的cell
NSMutableArray *_onScreenCells; //重用的cell
UILabel *moreLabel; //上拉查看更多的标签
BOOL isDownLoading;
NSArray *_images;
}
@property (nonatomic ,retain,setter = setImages:)NSArray *images;
@property (nonatomic, retain) NSMutableDictionary *dicReuseCells;
@property (nonatomic, retain) NSMutableArray *onScreenCells;
- (void)setLoad; //以下这三个函数设置图片的loading状态的
- (BOOL)downLoading;
- (void)setEndLoad;
- (id)initWithFrame:(CGRect)frame target:(UIViewController*)target action:(SEL)act;
//获取重用的cell
- (QuYouAppWaterFlowCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
//将要移除屏幕的cell添加到可重用列表中
- (void)addCellToReuseQueue:(QuYouAppWaterFlowCell *)cell;
- (void)reloadImageViews;
@end
1)最上面的y1,y2,y3,y4是用于处理图片的位置的参数.
2) setImages:这个方法就是来设置图片的地址的集合。
2.下面介绍这些接口的实现方式
const int tagAddition = 100;
#import "QuYouAppWaterfallView.h"
#define ACTIVITYVIEWTAG 320
@implementation QuYouAppWaterfallView
@synthesize images = _images;
@synthesize dicReuseCells = _dicReuseCells, onScreenCells = _onScreenCells;
- (void)dealloc
{
[super dealloc];
[_onScreenCells release];
[imageArray release];
[moreLabel release];
[_images release];
self.images = nil;
}
- (id)initWithFrame:(CGRect)frame target:(UIViewController*)target action:(SEL)act
{
self = [super initWithFrame:frame];
if (self) {
targetVC = target;
action = act;
//_images = [[NSArray alloc]init];
self.showsVerticalScrollIndicator = NO;
self.delegate = self;
}
return self;
}
//获取重用的cell
- (QuYouAppWaterFlowCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier{
if(identifier == nil || identifier.length ==0)return nil;
NSMutableArray *arrIndentifier_ = [_dicReuseCells objectForKey:identifier];
if(arrIndentifier_ && [arrIndentifier_ isKindOfClass:[NSArray class]] && arrIndentifier_.count > 0){
//找到了重用的
QuYouAppWaterFlowCell *cell_ = [arrIndentifier_ lastObject];
[arrIndentifier_ removeLastObject];
return cell_;
}
return nil;
}
//将要移除屏幕的cell添加到可重用列表中
- (void)addCellToReuseQueue:(QuYouAppWaterFlowCell *)cell
{
if(cell.strReuseIndentifier.length == 0) return ;
if(self.dicReuseCells == nil){
self.dicReuseCells = [NSMutableDictionary dictionaryWithCapacity:3];
NSMutableArray *arr_ = [NSMutableArray arrayWithObject:cell];
[_dicReuseCells setObject:arr_ forKey:cell.strReuseIndentifier];
}else
{
NSMutableArray *arr_ = [_dicReuseCells objectForKey:cell.strReuseIndentifier];
if(arr_ == nil){
arr_ = [NSMutableArray arrayWithObject:cell];
[_dicReuseCells setObject:arr_ forKey:cell.strReuseIndentifier];
}
else {
[arr_ addObject:cell];
}
}
}
- (void)setImages:(NSArray*)images{
if (_images != nil)
{
[_images release];
}
_images = [images retain];
if(!_onScreenCells) _onScreenCells = [[NSMutableArray alloc]init];
float offsetY = 4;
if (_images) {
if (!imageArray) imageArray = [[NSMutableArray alloc]init];
[imageArray removeAllObjects];
y1 = offsetY; y2 = offsetY; y3 = offsetY;
for (NSDictionary *picDic in self.images) {
//find the smallest y in y1, y2, y3, y4
float tempY = y1; int caseValue = 0;
if (tempY>y2) { tempY = y2; caseValue = 1; }
if (tempY>y3) { tempY = y3; caseValue = 2; }
float h = [[picDic objectForKey:@"pic_height"]floatValue]/2;
int x = 5 + caseValue%3*105;
// int x = 10 + caseValue%2*155;
float y = 0;
switch (caseValue)
{
case 0:
y = y1;
y1 = y1 + h + 4;
break;
case 1:
y = y2;
y2 = y2+ h + 4;
break;
case 2:
y = y3;
y3 = y3 + h + 4;
break;
default:
break;
}
[imageArray addObject:[NSArray arrayWithObjects:[NSNumber numberWithFloat:x], [NSNumber numberWithFloat:y], [NSNumber numberWithFloat:h],[picDic objectForKey:@"pic_url"], nil]];
if (y1 -50 > self.frame.size.height && y2-50 > self.frame.size.height && y3 -50 > self.frame.size.height) continue;
// if (y1 -75 > self.frame.size.height && y2-75 > self.frame.size.height ) continue;
QuYouAppWaterFlowCell* imageView;
imageView = [[QuYouAppWaterFlowCell alloc] initWithIdentifier:@"QuYouAppWaterFlowCell_Identifier"];
[imageView setFrame:CGRectMake(x, y, 100, h)];
imageView.tag = tagAddition+[images indexOfObject:picDic];
DLog(@"%@",[picDic objectForKey:@"pic_url"]);
// [imageView setImageWithURL:[NSURL URLWithString:[picDic objectForKey:@"pic_url"]]];
[imageView setImageWithURL:[NSURL URLWithString:[picDic objectForKey:@"pic_url"]] placeholderImage:[UIImage imageNamed:@"adorablePictrue_picbackground"]];
[self addSubview:imageView];
[imageView setUserInteractionEnabled:YES];
imageView.backgroundColor = [UIColor whiteColor];//保证在图片未加载出来之前能接受滑动手势
imageView.layer.borderWidth = 2;
imageView.layer.borderColor = [UIColor whiteColor].CGColor;
UITapGestureRecognizer *tapOne = [[UITapGestureRecognizer alloc] initWithTarget:targetVC action:action];
[imageView addGestureRecognizer:tapOne];
[tapOne release];
[_onScreenCells addObject:imageView];
}
float tempY = y1;
if (tempY<y2) tempY = y2;
if (tempY<y3) tempY = y3;
moreLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, tempY, 320, 20)];
moreLabel.textColor= [UIColor blackColor];
moreLabel.textAlignment = UITextAlignmentCenter;
moreLabel.font = [UIFont systemFontOfSize:14];
moreLabel.backgroundColor = [UIColor clearColor];
moreLabel.text = @"上拉看更多...";
[self addSubview:moreLabel];
[self setContentSize:CGSizeMake(self.frame.size.width, (tempY +20> self.frame.size.height ? tempY +20: self.frame.size.height+1))];
}
DLog(@"init [self.onScreenCells count]: %d",[self.onScreenCells count]);
}
- (void)reloadImageViews{
CGPoint offset = self.contentOffset;
if (!reuseQueue) {
reuseQueue = [NSMutableArray array];
}
//移掉划出屏幕外的图片
NSMutableArray *readyToRemove = [NSMutableArray array];
for (QuYouAppWaterFlowCell *view in _onScreenCells) {
if((view.frame.origin.y + view.frame.size.height - offset.y) < 0.0001 || (view.frame.origin.y - self.frame.size.height - offset.y) > 0.0001){
[readyToRemove addObject:view];
}
}
for (QuYouAppWaterFlowCell *view in readyToRemove) {
QuYouAppWaterFlowCell *imageView = (QuYouAppWaterFlowCell*)view;
[imageView cancelCurrentImageLoad];
[_onScreenCells removeObject:view];
[view removeFromSuperview];
[self addCellToReuseQueue:view];
}
//遍历图片数组
for (NSArray *imageInfo in imageArray) {
int tagIndex = [imageArray indexOfObject:imageInfo];
float imageX = [[imageInfo objectAtIndex:0] floatValue]; //图片原点x
float imageY = [[imageInfo objectAtIndex:1] floatValue]; //图片原点y
float imageYH = imageY + [[imageInfo objectAtIndex:2] floatValue];
BOOL OnScreen = FALSE;
if (imageY <= offset.y && imageYH >= offset.y) OnScreen = TRUE;
if (imageY >= offset.y && imageY <= (offset.y + self.frame.size.height)) OnScreen = TRUE;
//在屏幕范围内的创建添加
if (OnScreen) {
BOOL HasOnScreen = FALSE;
for (QuYouAppWaterFlowCell *vi in _onScreenCells) {
if (tagIndex+tagAddition == vi.tag)HasOnScreen = TRUE;
}
if (!HasOnScreen) {
QuYouAppWaterFlowCell *imageView = [self dequeueReusableCellWithIdentifier:@"QuYouAppWaterFlowCell_Identifier"];
if(imageView == nil)
{
imageView = [[QuYouAppWaterFlowCell alloc] initWithIdentifier:@"QuYouAppWaterFlowCell_Identifier"];
[imageView setUserInteractionEnabled:YES];
imageView.backgroundColor = [UIColor blackColor];//保证在图片未加载出来之前能接受滑动手势
imageView.layer.borderWidth = 2;
imageView.layer.borderColor = [UIColor whiteColor].CGColor;
UITapGestureRecognizer *tapOne = [[UITapGestureRecognizer alloc] initWithTarget:targetVC action:action];
[imageView addGestureRecognizer:tapOne];
[tapOne release];
}
else
{
//NSLog(@"此条是从重用列表中获取的。。。。。");
[imageView setImage:nil];
}
[imageView setFrame:CGRectMake(imageX, imageY, 100, imageYH-imageY)];
imageView.tag = tagIndex+tagAddition;
[imageView setImageWithURL:[NSURL URLWithString:[imageInfo lastObject]]];
[self addSubview:imageView];
[_onScreenCells addObject:imageView];
}
}
}
}
- (void)setLoad
{
UIActivityIndicatorView *acitivityView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[acitivityView startAnimating];
[acitivityView setFrame:CGRectMake(50, 2, 16, 16)];
acitivityView.tag = ACTIVITYVIEWTAG;
[moreLabel addSubview:acitivityView];
[acitivityView release];
isDownLoading = YES;
}
- (BOOL)downLoading
{
return isDownLoading;
}
- (void)setEndLoad
{
UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[moreLabel viewWithTag:ACTIVITYVIEWTAG];
[activityView stopAnimating];
[activityView removeFromSuperview];
isDownLoading = NO;
}
#if 0
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[self reloadImageViews];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self reloadImageViews];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
}
#endif
@end
//-------------------------------------------------------------------------------------------------------------------------------
//
//
//QuYouAppWaterFlowCell
//
//-------------------------------------------------------------------------------------------------------------------------------
@implementation QuYouAppWaterFlowCell
@synthesize indexPath = _indexPath;
@synthesize strReuseIndentifier = _strReuseIndentifier;
-(id)initWithIdentifier:(NSString *)indentifier
{
if(self = [super init])
{
self.strReuseIndentifier = indentifier;
}
return self;
}
@end
上面的代码其实已经足够清晰了,核心的函数其实只有2个setImage: 这个函数在你获取所有的图片列表的时候调用,reloadImages 在你滚动的时候不断的去调用,来计算图片的位置,是不要要移除出屏幕。
3.总结
这段代码也不是我原创,我只是在这基础上稍有改进。主要思路就是重用内存。当然即便如此,在实际的使用过程中,可能会存在卡的现象,可能和图片的大小有关或者网络情况。还有一定的可优化的空间,比如在reloadImages这个函数上面可以做一些优化,减少运算的次数。
其次,在使用过程中用到第三方的图片框架叫SDWebImage,当然你也可以使用其他的库。