首页 > 代码库 > iOS开发UI篇—自定义瀑布流控件(cell的循环利用)

iOS开发UI篇—自定义瀑布流控件(cell的循环利用)

iOS开发UI篇—自定义瀑布流控件(cell的循环利用)

一、简单说明

  当滚动的时候,向数据源要cell。

  当UIScrollView滚动的时候会调用layoutSubviews在tableView中也是一样的,因此,可以用这个方法来监听scrollView的滚动,可以在在这个地方向数据源索要对应位置的cell(frame在屏幕上的cell)。
示例:
  当scrollView在屏幕上滚动的时候,离开屏幕的cell应该放到缓存池中去,询问即将(已经)进入到屏幕的cell,对于还没有进入到屏幕的cell不作处理。
 
判断cell有没有在屏幕上?
  cell的最大的Y值>contentoffset的y值,并且小于contentoffset的y值+UIView的高度

 代码示例:

 1 /** 2  *  当UIScrollView滚动的时候也会调用这个方法 3  */ 4 -(void)layoutSubviews 5 { 6     [super layoutSubviews]; 7     NSLog(@"%d",self.subviews.count); 8      9     //向数据源索要对应位置的cell10     NSUInteger numberOfCells=self.cellFrames.count;11     for (int i=0; i<numberOfCells; i++) {12         //取出i位置的frame,注意转换13         CGRect cellFrame=[self.cellFrames[i] CGRectValue];14         15         //判断i位置对应的frame在不在屏幕上(能否看见)16         if ([self isInScreen:cellFrame]) {//在屏幕上17             YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];18             cell.frame=cellFrame;19             [self addSubview:cell];20         }else //不在屏幕上21         {22             23         }24     }25 }26 #pragma mark-私有方法27 /**28  *  判断一个人cell的frame有没有显示在屏幕上29  */30 -(BOOL)isInScreen:(CGRect)frame31 {32     return (CGRectGetMaxY(frame)>self.contentOffset.y)&&(CGRectGetMaxY(frame)<self.contentOffset.y+self.frame.size.height);33 }

上述代码存在一个容易忽视的问题,就是当用户在短距离之内来回拖动cell的时候,cell依然会创建新的cell并切换。

解决这个问题,可以考虑添加一个字典属性,把位置(i)和这个位置的cell存入到字典中,在创建cell(向数据源要数据)之前进行判断,如果该位置的cell存在,那么就不创建。

修正后的代码如下:

 1 /** 2  *  当UIScrollView滚动的时候也会调用这个方法 3  */ 4 -(void)layoutSubviews 5 { 6     [super layoutSubviews]; 7     NSLog(@"%d",self.subviews.count); 8      9     //向数据源索要对应位置的cell10     NSUInteger numberOfCells=self.cellFrames.count;11     for (int i=0; i<numberOfCells; i++) {12         //取出i位置的frame,注意转换13         CGRect cellFrame=[self.cellFrames[i] CGRectValue];14         15         //判断i位置对应的frame在不在屏幕上(能否看见)16         if ([self isInScreen:cellFrame]) {//在屏幕上17           18             //优先从字典中取出i位置的cell19             YYWaterflowViewCell *cell=self.displayingCells[@(i)];20             if (cell==nil) {21                cell= [self.dadaSource waterflowView:self cellAtIndex:i];22                 cell.frame=cellFrame;23                 [self addSubview:cell];24                 25                 //存放在字典中26                 self.displayingCells[@(i)]=cell;27             }28             29         }else //不在屏幕上30         {31             32         }33     }34 }

 

二、cell的循环利用

说明:使用set集合实现一个缓存池,当cell离开显示界面的时候,就把这个cell放到缓存池中,当下次使用的时候,直接去缓存池中取。

注意:放到缓存池中的cell是给控制器用的。

需要提供一个方法,仿照tableView根据标识去缓存池中查找可以循环利用的cell

   实现代码:

YYWaterflowView.h文件

 1 // 2 //  YYWaterflowView.h 3 //  06-瀑布流 4 // 5 //  Created by apple on 14-7-29. 6 //  Copyright (c) 2014年 wendingding. All rights reserved. 7 // 8  9 #import <UIKit/UIKit.h>10 11 //使用瀑布流形式展示内容的控件12 typedef enum {13     YYWaterflowViewMarginTypeTop,14     YYWaterflowViewMarginTypeBottom,15     YYWaterflowViewMarginTypeLeft,16     YYWaterflowViewMarginTypeRight,17     YYWaterflowViewMarginTypeColumn,//每一列18     YYWaterflowViewMarginTypeRow,//每一行19 20 }YYWaterflowViewMarginType;21 22 @class YYWaterflowViewCell,YYWaterflowView;23 24 /**25  *  1.数据源方法26  */27 @protocol YYWaterflowViewDataSource <NSObject>28 //要求强制实现29 @required30 /**31  * (1)一共有多少个数据32  */33 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView;34 /**35  *  (2)返回index位置对应的cell36  */37 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index;38 39 //不要求强制实现40 @optional41 /**42  *  (3)一共有多少列43  */44 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView;45 46 @end47 48 49 /**50  *  2.代理方法51  */52 @protocol YYWaterflowViewDelegate <UIScrollViewDelegate>53 //不要求强制实现54 @optional55 /**56  *  (1)第index位置cell对应的高度57  */58 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;59 /**60  *  (2)选中第index位置的cell61  */62 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index;63 /**64  *  (3)返回间距65  */66 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type;67 @end68 69 70 /**71  *  3.瀑布流控件72  */73 @interface YYWaterflowView : UIScrollView74 /**75  *  (1)数据源76  */77 @property(nonatomic,weak)id<YYWaterflowViewDataSource> dadaSource;78 /**79  *  (2)代理80  */81 @property(nonatomic,weak)id<YYWaterflowViewDelegate> delegate;82 83 #pragma mark-公共方法84 /**85  *  刷新数据86  */87 -(void)reloadData;88 /**89  *  根据标识去缓存池中查找可循环利用的cell90  */91 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;92 @end

核心代码:

YYWaterflowView.m文件

  1 //  2 //  YYWaterflowView.m  3 //  06-瀑布流  4 //  5 //  Created by apple on 14-7-29.  6 //  Copyright (c) 2014年 wendingding. All rights reserved.  7 //  8   9 #import "YYWaterflowView.h" 10 #import "YYWaterflowViewCell.h" 11 #define YYWaterflowViewDefaultNumberOfClunms  3 12 #define YYWaterflowViewDefaultCellH  100 13 #define YYWaterflowViewDefaultMargin 10 14  15 @interface YYWaterflowView() 16 /** 17  *  所有cell的frame数据 18  */ 19 @property(nonatomic,strong)NSMutableArray *cellFrames; 20 /** 21  *  正在展示的cell 22  */ 23 @property(nonatomic,strong)NSMutableDictionary  *displayingCells; 24 /** 25  *  缓存池(使用SET) 26  */ 27 @property(nonatomic,strong)NSMutableSet *reusableCells; 28 @end 29  30 @implementation YYWaterflowView 31  32 #pragma mark-懒加载 33 -(NSMutableArray *)cellFrames 34 { 35     if (_cellFrames==nil) { 36         _cellFrames=[NSMutableArray array]; 37     } 38     return _cellFrames; 39 } 40  41 -(NSMutableDictionary *)displayingCells 42 { 43     if (_displayingCells==nil) { 44         _displayingCells=[NSMutableDictionary dictionary]; 45     } 46     return _displayingCells; 47 } 48  49 -(NSMutableSet *)reusableCells 50 { 51     if (_reusableCells==nil) { 52         _reusableCells=[NSMutableSet set]; 53     } 54     return _reusableCells; 55 } 56  57 - (id)initWithFrame:(CGRect)frame 58 { 59     self = [super initWithFrame:frame]; 60     if (self) { 61     } 62     return self; 63 } 64  65 /** 66  *  刷新数据 67  *  1.计算每个cell的frame 68  */ 69 -(void)reloadData 70 { 71     //cell的总数是多少 72     int numberOfCells=[self.dadaSource numberOfCellsInWaterflowView:self]; 73      74     //cell的列数 75     int numberOfColumns=[self numberOfColumns]; 76      77     //间距 78     CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft]; 79     CGFloat rightM=[self marginForType:YYWaterflowViewMarginTypeRight]; 80     CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn]; 81     CGFloat topM=[self marginForType:YYWaterflowViewMarginTypeTop]; 82     CGFloat rowM=[self marginForType:YYWaterflowViewMarginTypeRow]; 83     CGFloat bottomM=[self marginForType:YYWaterflowViewMarginTypeBottom]; 84      85     //(1)cell的宽度 86     //cell的宽度=(整个view的宽度-左边的间距-右边的间距-(列数-1)X每列之间的间距)/总列数 87     CGFloat cellW=(self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns; 88  89      90      91     //用一个C语言的数组来存放所有列的最大的Y值 92     CGFloat maxYOfColumns[numberOfColumns]; 93     for (int i=0; i<numberOfColumns; i++) { 94         //初始化数组的数值全部为0 95         maxYOfColumns[i]=0.0; 96     } 97      98      99     //计算每个cell的fram100     for (int i=0; i<numberOfCells; i++) {101         102         //(2)cell的高度103         //询问代理i位置的高度104         CGFloat cellH=[self heightAtIndex:i];105         106         //cell处在第几列(最短的一列)107         NSUInteger cellAtColumn=0;108         109         //cell所处那列的最大的Y值(当前最短的那一列的最大的Y值)110         //默认设置最短的一列为第一列(优化性能)111         CGFloat maxYOfCellAtColumn=maxYOfColumns[cellAtColumn];112         113         //求出最短的那一列114         for (int j=0; j<numberOfColumns; j++) {115             if (maxYOfColumns[j]<maxYOfCellAtColumn) {116                 cellAtColumn=j;117                 maxYOfCellAtColumn=maxYOfColumns[j];118             }119         }120         121         //(3)cell的位置(X,Y)122         //cell的X=左边的间距+列号*(cell的宽度+每列之间的间距)123         CGFloat cellX=leftM+cellAtColumn*(cellW +columnM);124         //cell的Y,先设定为0125         CGFloat cellY=0;126         if (maxYOfCellAtColumn==0.0) {//首行127             cellY=topM;128         }else129         {130             cellY=maxYOfCellAtColumn+rowM;131         }132         133         //设置cell的frame并添加到数组中134         CGRect cellFrame=CGRectMake(cellX, cellY, cellW, cellH);135         [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];136         137         //更新最短那一列的最大的Y值138         maxYOfColumns[cellAtColumn]=CGRectGetMaxY(cellFrame);139         140         //显示cell141 //        YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];142 //        cell.frame=cellFrame;143 //        [self addSubview:cell];144     }145     146     //设置contentSize147     CGFloat contentH=maxYOfColumns[0];148     for (int i=1; i<numberOfColumns; i++) {149         if (maxYOfColumns[i]>contentH) {150             contentH=maxYOfColumns[i];151         }152     }153     contentH += bottomM;154     self.contentSize=CGSizeMake(0, contentH);155 }156 157 /**158  *  当UIScrollView滚动的时候也会调用这个方法159  */160 -(void)layoutSubviews161 {162     [super layoutSubviews];163  164     165     //向数据源索要对应位置的cell166     NSUInteger numberOfCells=self.cellFrames.count;167     for (int i=0; i<numberOfCells; i++) {168         //取出i位置的frame,注意转换169         CGRect cellFrame=[self.cellFrames[i] CGRectValue];170         171         //优先从字典中取出i位置的cell172         YYWaterflowViewCell *cell=self.displayingCells[@(i)];173         174         //判断i位置对应的frame在不在屏幕上(能否看见)175         if ([self isInScreen:cellFrame]) {//在屏幕上176             if (cell==nil) {177                cell= [self.dadaSource waterflowView:self cellAtIndex:i];178                 cell.frame=cellFrame;179                 [self addSubview:cell];180                 181                 //存放在字典中182                 self.displayingCells[@(i)]=cell;183             }184             185         }else //不在屏幕上186         {187             if (cell) {188                 //从scrollView和字典中删除189                 [cell removeFromSuperview];190                 [self.displayingCells removeObjectForKey:@(i)];191                 192                 //存放进缓存池193                 [self.reusableCells addObject:cell];194             }195         }196     }197        NSLog(@"%d",self.subviews.count);198 }199 200 -(id)dequeueReusableCellWithIdentifier:(NSString *)identifier201 {202    __block YYWaterflowViewCell *reusableCell=nil;203     [self.reusableCells enumerateObjectsUsingBlock:^(YYWaterflowViewCell *cell, BOOL *stop) {204         if ([cell.identifier isEqualToString:identifier]) {205             reusableCell=cell;206             *stop=YES;207         }208     }];209     210     if (reusableCell) {//从缓存池中移除(已经用掉了)211         [self.reusableCells removeObject:reusableCell];212     }213     return reusableCell;214 }215 216 #pragma mark-私有方法217 /**218  *  判断一个人cell的frame有没有显示在屏幕上219  */220 -(BOOL)isInScreen:(CGRect)frame221 {222 //    return (CGRectGetMaxY(frame)>self.contentOffset.y)&&(CGRectGetMaxY(frame)<self.contentOffset.y+self.frame.size.height);223     return (CGRectGetMaxY(frame) > self.contentOffset.y) &&224     (CGRectGetMinY(frame) < self.contentOffset.y + self.frame.size.height);225 226 }227 -(CGFloat)marginForType:(YYWaterflowViewMarginType)type228 {229     if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {230        return  [self.delegate waterflowView:self marginForType:type];231     }else232     {233         return YYWaterflowViewDefaultMargin;234     }235 }236 237 -(NSUInteger)numberOfColumns238 {239     if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {240         return [self.dadaSource numberOfColumnsInWaterflowView:self];241     }else242     {243         return  YYWaterflowViewDefaultNumberOfClunms;244     }245 }246 247 -(CGFloat)heightAtIndex:(NSUInteger)index248 {249     if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {250         return [self.delegate waterflowView:self heightAtIndex:index];251     }else252     {253         return YYWaterflowViewDefaultCellH;254     }255 }256 @end

 YYWaterflowViewCell.h文件

 1 // 2 //  YYWaterflowViewCell.h 3 //  06-瀑布流 4 // 5 //  Created by apple on 14-7-29. 6 //  Copyright (c) 2014年 wendingding. All rights reserved. 7 // 8  9 #import <UIKit/UIKit.h>10 11 @interface YYWaterflowViewCell : UIView12 @property(nonatomic,copy)NSString *identifier;13 @end

控制器中cell的处理

YYViewController.m文件

 1 // 2 //  YYViewController.m 3 //  06-瀑布流 4 // 5 //  Created by apple on 14-7-28. 6 //  Copyright (c) 2014年 wendingding. All rights reserved. 7 // 8  9 #import "YYViewController.h"10 #import "YYWaterflowView.h"11 #import "YYWaterflowViewCell.h"12 13 @interface YYViewController ()<YYWaterflowViewDelegate,YYWaterflowViewDataSource>14 15 @end16 17 @implementation YYViewController18 19 - (void)viewDidLoad20 {21     [super viewDidLoad];22     YYWaterflowView *waterflow=[[YYWaterflowView alloc]init];23     waterflow.frame=self.view.bounds;24     waterflow.delegate=self;25     waterflow.dadaSource=self;26     [self.view addSubview:waterflow];27     28     //刷新数据29     [waterflow reloadData];30 }31 32 #pragma mark-数据源方法33 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView34 {35     return 40;36 }37 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView38 {39     return 3;40 }41 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index42 {43 //    YYWaterflowViewCell *cell=[[YYWaterflowViewCell alloc]init];44 45     static NSString *ID=@"cell";46     YYWaterflowViewCell *cell=[waterflowView dequeueReusableCellWithIdentifier:ID];47     if (cell==nil) {48         cell=[[YYWaterflowViewCell alloc]init];49         cell.identifier=ID;50         //给cell设置一个随机色51         cell.backgroundColor=YYRandomColor;52         [cell addSubview:[UIButton buttonWithType:UIButtonTypeContactAdd]];53     }54  55     return cell;56 }57 58 59 #pragma mark-代理方法60 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index61 {62     switch (index%3) {63         case 0:return 90;64         case 1:return 110;65         case 2:return 80;66         default:return 120;67     }68 }69 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type70 {71     switch (type) {72         case YYWaterflowViewMarginTypeTop:73         case YYWaterflowViewMarginTypeBottom:74         case YYWaterflowViewMarginTypeLeft:75         case YYWaterflowViewMarginTypeRight:76             return 10;77         case YYWaterflowViewMarginTypeColumn:78         case YYWaterflowViewMarginTypeRow:79             return 5;80     }81 }82 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index83 {84     NSLog(@"点击了%d的cell",index);85 }86 @end

实现效果:

打印查看Cell的创建数量: