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的建立數量: