上篇说到瀑布流,我是用的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,当然你也可以使用其他的库。