您的位置:首页 > 产品设计 > UI/UE

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

2014-12-30 18:13 447 查看
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     //向数据源索要对应位置的cell
10     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)frame
31 {
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     //向数据源索要对应位置的cell
10     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位置的cell
19             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 @required
30 /**
31  * (1)一共有多少个数据
32  */
33 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView;
34 /**
35  *  (2)返回index位置对应的cell
36  */
37 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index;
38
39 //不要求强制实现
40 @optional
41 /**
42  *  (3)一共有多少列
43  */
44 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView;
45
46 @end
47
48
49 /**
50  *  2.代理方法
51  */
52 @protocol YYWaterflowViewDelegate <UIScrollViewDelegate>
53 //不要求强制实现
54 @optional
55 /**
56  *  (1)第index位置cell对应的高度
57  */
58 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;
59 /**
60  *  (2)选中第index位置的cell
61  */
62 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index;
63 /**
64  *  (3)返回间距
65  */
66 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type;
67 @end
68
69
70 /**
71  *  3.瀑布流控件
72  */
73 @interface YYWaterflowView : UIScrollView
74 /**
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  *  根据标识去缓存池中查找可循环利用的cell
90  */
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的fram
100     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,先设定为0
125         CGFloat cellY=0;
126         if (maxYOfCellAtColumn==0.0) {//首行
127             cellY=topM;
128         }else
129         {
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         //显示cell
141 //        YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];
142 //        cell.frame=cellFrame;
143 //        [self addSubview:cell];
144     }
145
146     //设置contentSize
147     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)layoutSubviews
161 {
162     [super layoutSubviews];
163
164
165     //向数据源索要对应位置的cell
166     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位置的cell
172         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 *)identifier
201 {
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)frame
221 {
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)type
228 {
229     if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
230        return  [self.delegate waterflowView:self marginForType:type];
231     }else
232     {
233         return YYWaterflowViewDefaultMargin;
234     }
235 }
236
237 -(NSUInteger)numberOfColumns
238 {
239     if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
240         return [self.dadaSource numberOfColumnsInWaterflowView:self];
241     }else
242     {
243         return  YYWaterflowViewDefaultNumberOfClunms;
244     }
245 }
246
247 -(CGFloat)heightAtIndex:(NSUInteger)index
248 {
249     if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
250         return [self.delegate waterflowView:self heightAtIndex:index];
251     }else
252     {
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 : UIView
12 @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 @end
16
17 @implementation YYViewController
18
19 - (void)viewDidLoad
20 {
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 *)waterflowView
34 {
35     return 40;
36 }
37 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView
38 {
39     return 3;
40 }
41 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
42 {
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)index
61 {
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)type
70 {
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)index
83 {
84     NSLog(@"点击了%d的cell",index);
85 }
86 @end




实现效果:



打印查看Cell的创建数量:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐