iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

时间:2023-01-29 00:36:43

转自:http://www.cnblogs.com/wendingding/p/3881485.html

一、简单说明

关于瀑布流

1.是使用UIScrollView实现的 2.刷新数据(reloadData)方法里面做哪些事情 3.layoutSubviews方法里面做哪些事情 4.模仿UItableView进行设计 完善: 瀑布流控件第一次显示到屏幕上的时候自动的向数据源索要数据,而不需要手动调用。这需要监听View的显示,View的显示有一个方法,叫做willMoveToSuperview:在该方法中直接刷新一次数据即可。    iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)   二、把自定义的瀑布流控件作为一个框架进行使用 1.框架 把自定义的瀑布流控件转变成一个框架。以后可以把它作为一套框架进行开发。    iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流) 2.瀑布流框架的使用 在做瀑布流应用的时候,一定要和服务器开发人员沟通清楚,提供的数据一定要包括图片的宽度和高度,否则没有办法进行处理。 为了保证图片不会变形,因此不能直接返回图片的宽度或者是高度,而应该使用宽高的比值。应该根据cell的宽度,有图片的宽高比计算出图片的高度(正式的高度!=真实的高度  cellW/cellH=真实的宽度/真实的高度==>cellH=cellW*真实的高度/真实的宽度)。 需要在框架中提供一个接口,获取cell的宽度。  YYWaterflowView.h文件 iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 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 *cell的宽度
86 */
87 -(CGFloat)cellWidth;
88 /**
89 * 刷新数据
90 */
91 -(void)reloadData;
92 /**
93 * 根据标识去缓存池中查找可循环利用的cell
94 */
95 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
96 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

YYWaterflowView.m文件

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
  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 -(void)willMoveToSuperview:(UIView *)newSuperview
66 {
67 [self reloadData];
68 }
69
70 #pragma mark-公共方法
71 /**
72 * cell的宽度
73 */
74 -(CGFloat)cellWidth
75 {
76 //cell的列数
77 int numberOfColumns=[self numberOfColumns];
78 CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft];
79 CGFloat rightM=[self marginForType:YYWaterflowViewMarginTypeRight];
80 CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn];
81 return (self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns;
82 }
83
84 /**
85 * 刷新数据
86 * 1.计算每个cell的frame
87 */
88 -(void)reloadData
89 {
90 //cell的总数是多少
91 int numberOfCells=[self.dadaSource numberOfCellsInWaterflowView:self];
92
93 //cell的列数
94 int numberOfColumns=[self numberOfColumns];
95
96 //间距
97 CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft];
98
99 CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn];
100 CGFloat topM=[self marginForType:YYWaterflowViewMarginTypeTop];
101 CGFloat rowM=[self marginForType:YYWaterflowViewMarginTypeRow];
102 CGFloat bottomM=[self marginForType:YYWaterflowViewMarginTypeBottom];
103
104 //(1)cell的宽度
105 //cell的宽度=(整个view的宽度-左边的间距-右边的间距-(列数-1)X每列之间的间距)/总列数
106 // CGFloat cellW=(self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns;
107 CGFloat cellW=[self cellWidth];
108
109 //用一个C语言的数组来存放所有列的最大的Y值
110 CGFloat maxYOfColumns[numberOfColumns];
111 for (int i=0; i<numberOfColumns; i++) {
112 //初始化数组的数值全部为0
113 maxYOfColumns[i]=0.0;
114 }
115
116
117 //计算每个cell的fram
118 for (int i=0; i<numberOfCells; i++) {
119
120 //(2)cell的高度
121 //询问代理i位置的高度
122 CGFloat cellH=[self heightAtIndex:i];
123
124 //cell处在第几列(最短的一列)
125 NSUInteger cellAtColumn=0;
126
127 //cell所处那列的最大的Y值(当前最短的那一列的最大的Y值)
128 //默认设置最短的一列为第一列(优化性能)
129 CGFloat maxYOfCellAtColumn=maxYOfColumns[cellAtColumn];
130
131 //求出最短的那一列
132 for (int j=0; j<numberOfColumns; j++) {
133 if (maxYOfColumns[j]<maxYOfCellAtColumn) {
134 cellAtColumn=j;
135 maxYOfCellAtColumn=maxYOfColumns[j];
136 }
137 }
138
139 //(3)cell的位置(X,Y)
140 //cell的X=左边的间距+列号*(cell的宽度+每列之间的间距)
141 CGFloat cellX=leftM+cellAtColumn*(cellW +columnM);
142 //cell的Y,先设定为0
143 CGFloat cellY=0;
144 if (maxYOfCellAtColumn==0.0) {//首行
145 cellY=topM;
146 }else
147 {
148 cellY=maxYOfCellAtColumn+rowM;
149 }
150
151 //设置cell的frame并添加到数组中
152 CGRect cellFrame=CGRectMake(cellX, cellY, cellW, cellH);
153 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
154
155 //更新最短那一列的最大的Y值
156 maxYOfColumns[cellAtColumn]=CGRectGetMaxY(cellFrame);
157
158 //显示cell
159 // YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];
160 // cell.frame=cellFrame;
161 // [self addSubview:cell];
162 }
163
164 //设置contentSize
165 CGFloat contentH=maxYOfColumns[0];
166 for (int i=1; i<numberOfColumns; i++) {
167 if (maxYOfColumns[i]>contentH) {
168 contentH=maxYOfColumns[i];
169 }
170 }
171 contentH += bottomM;
172 self.contentSize=CGSizeMake(0, contentH);
173 }
174
175 /**
176 * 当UIScrollView滚动的时候也会调用这个方法
177 */
178 -(void)layoutSubviews
179 {
180 [super layoutSubviews];
181
182
183 //向数据源索要对应位置的cell
184 NSUInteger numberOfCells=self.cellFrames.count;
185 for (int i=0; i<numberOfCells; i++) {
186 //取出i位置的frame,注意转换
187 CGRect cellFrame=[self.cellFrames[i] CGRectValue];
188
189 //优先从字典中取出i位置的cell
190 YYWaterflowViewCell *cell=self.displayingCells[@(i)];
191
192 //判断i位置对应的frame在不在屏幕上(能否看见)
193 if ([self isInScreen:cellFrame]) {//在屏幕上
194 if (cell==nil) {
195 cell= [self.dadaSource waterflowView:self cellAtIndex:i];
196 cell.frame=cellFrame;
197 [self addSubview:cell];
198
199 //存放在字典中
200 self.displayingCells[@(i)]=cell;
201 }
202
203 }else //不在屏幕上
204 {
205 if (cell) {
206 //从scrollView和字典中删除
207 [cell removeFromSuperview];
208 [self.displayingCells removeObjectForKey:@(i)];
209
210 //存放进缓存池
211 [self.reusableCells addObject:cell];
212 }
213 }
214 }
215 // NSLog(@"%d",self.subviews.count);
216 }
217
218 -(id)dequeueReusableCellWithIdentifier:(NSString *)identifier
219 {
220 __block YYWaterflowViewCell *reusableCell=nil;
221 [self.reusableCells enumerateObjectsUsingBlock:^(YYWaterflowViewCell *cell, BOOL *stop) {
222 if ([cell.identifier isEqualToString:identifier]) {
223 reusableCell=cell;
224 *stop=YES;
225 }
226 }];
227
228 if (reusableCell) {//从缓存池中移除(已经用掉了)
229 [self.reusableCells removeObject:reusableCell];
230 }
231 return reusableCell;
232 }
233
234 #pragma mark cell的事件处理
235 -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
236 {
237 //如果没有点击事件的代理方法,那么就直接返回
238 if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)])
239 return;
240
241 //获得手指在屏幕上点击的触摸点
242 UITouch *touch=[touches anyObject];
243 CGPoint point1=[touch locationInView:touch.view];
244 CGPoint point=[touch locationInView:self];
245 NSLog(@"%@--%@",NSStringFromCGPoint(point),NSStringFromCGPoint(point1));
246
247 __block NSNumber *selectIndex=nil;
248 [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, YYWaterflowViewCell *cell, BOOL *stop) {
249 if (CGRectContainsPoint(cell.frame, point)) {
250 selectIndex=key;
251 *stop=YES;
252 }
253 }];
254 if (selectIndex) {
255 //需要转换
256 [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue];
257 }
258
259 }
260 #pragma mark-私有方法
261 /**
262 * 判断一个人cell的frame有没有显示在屏幕上
263 */
264 -(BOOL)isInScreen:(CGRect)frame
265 {
266 // return (CGRectGetMaxY(frame)>self.contentOffset.y)&&(CGRectGetMaxY(frame)<self.contentOffset.y+self.frame.size.height);
267 return (CGRectGetMaxY(frame) > self.contentOffset.y) &&
268 (CGRectGetMinY(frame) < self.contentOffset.y + self.frame.size.height);
269
270 }
271 -(CGFloat)marginForType:(YYWaterflowViewMarginType)type
272 {
273 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
274 return [self.delegate waterflowView:self marginForType:type];
275 }else
276 {
277 return YYWaterflowViewDefaultMargin;
278 }
279 }
280
281 -(NSUInteger)numberOfColumns
282 {
283 if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
284 return [self.dadaSource numberOfColumnsInWaterflowView:self];
285 }else
286 {
287 return YYWaterflowViewDefaultNumberOfClunms;
288 }
289 }
290
291 -(CGFloat)heightAtIndex:(NSUInteger)index
292 {
293 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
294 return [self.delegate waterflowView:self heightAtIndex:index];
295 }else
296 {
297 return YYWaterflowViewDefaultCellH;
298 }
299 }
300 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
提示:瀑布流有一个特点,即每个cell的宽度都是一样的。   三、蘑菇街的实现 1.新建一个项目,使用自定义的瀑布流框架   观察plist文件的数据结构   iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流) 新建一个shop模型,继承自NSObject类, iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流) 该类中的代码设计如下: YYShop.h文件 iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 1 //
2 // YYShop.h
3 // 06-瀑布流
4 //
5 // Created by apple on 14-7-31.
6 // Copyright (c) 2014年 wendingding. All rights reserved.
7 //
8
9 #import <Foundation/Foundation.h>
10
11 @interface YYShop : NSObject
12 /**
13 * 图片的高度
14 */
15 @property(nonatomic,assign)CGFloat h;
16 /**
17 * 图片的宽度
18 */
19 @property(nonatomic,assign)CGFloat w;
20 /**
21 * 图片的网络地址
22 */
23 @property(nonatomic,copy)NSString *img;
24 /**
25 * 商品的价格
26 */
27 @property(nonatomic,copy)NSString *price;
28 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

控制器中的代码设计和处理:

 YYShopViewController.m文件

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 1 //
2 // YYShopViewController.m
3 // 06-瀑布流
4 //
5 // Created by apple on 14-7-31.
6 // Copyright (c) 2014年 wendingding. All rights reserved.
7 //
8
9 #import "YYShopViewController.h"
10 #import "YYWaterflowView.h"
11 #import "YYWaterflowViewCell.h"
12 #import "YYShop.h"
13 #import "YYShopCell.h"
14 #import "MJExtension.h"
15
16 @interface YYShopViewController ()<YYWaterflowViewDataSource,YYWaterflowViewDelegate>
17 @property(nonatomic,strong)NSMutableArray *shops;
18 @end
19
20 @implementation YYShopViewController
21
22 #pragma mark-懒加载
23 -(NSMutableArray *)shops
24 {
25 if (_shops==nil) {
26 _shops=[NSMutableArray array];
27 }
28 return _shops;
29 }
30 - (void)viewDidLoad
31 {
32 [super viewDidLoad];
33 //1.初始化数据
34 NSArray *newShop=[YYShop objectArrayWithFilename:@"1.plist"];
35 [self.shops addObjectsFromArray:newShop];
36
37 //2.创建一个瀑布流
38 YYWaterflowView *waterflow=[[YYWaterflowView alloc]init];
39 waterflow.frame=self.view.bounds;
40 waterflow.delegate=self;
41 waterflow.dadaSource=self;
42 [self.view addSubview:waterflow];
43 }
44 #pragma mark-数据源方法
45 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView
46 {
47 return 40;
48 }
49 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView
50 {
51 return 3;
52 }
53 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
54 {
55 YYShopCell *cell=[YYShopCell cellWithwaterflowView:waterflowView];
56 cell.shop=self.shops[index];
57 return cell;
58 }
59
60
61 #pragma mark-代理方法
62 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index
63 {
64 YYShop *shop=self.shops[index];
65 //根据Cell的宽度和图片的宽高比 算出cell的高度
66 return waterflowView.cellWidth*shop.h/shop.w;
67 }
68
69 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index
70 {
71 NSLog(@"点击了第%d个cell",index);
72 }
73
74
75 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

对瀑布流的cell进行自定义,按照需要的方式处理cell中的子控件(这里包括imageView和label控件)

自定义cell的代码处理如下:

YYShopCell.h文件

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 1 //
2 // YYShopCell.h
3 // 06-瀑布流
4 // Created by apple on 14-7-31.
5 // Copyright (c) 2014年 wendingding. All rights reserved.
6 //
7
8 #import "YYWaterflowViewCell.h"
9
10 @class YYWaterflowView,YYShop;
11 @interface YYShopCell : YYWaterflowViewCell
12 @property(nonatomic,strong)YYShop *shop;
13 +(instancetype)cellWithwaterflowView:(YYWaterflowView *)waterflowView;
14 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

YYShopCell.m文件

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 1 //
2 // YYShopCell.m
3 // 06-瀑布流
4 //
5 // Created by apple on 14-7-31.
6 // Copyright (c) 2014年 wendingding. All rights reserved.
7 //
8
9 #import "YYShopCell.h"
10 #import "YYWaterflowView.h"
11 #import "YYWaterflowViewCell.h"
12 #import "YYShop.h"
13 #import "UIImageView+WebCache.h"
14
15 @interface YYShopCell ()
16 @property(nonatomic,strong)UIImageView *imageView;
17 @property(nonatomic,strong)UILabel *priceLabel;
18 @end
19 @implementation YYShopCell
20
21 - (id)initWithFrame:(CGRect)frame
22 {
23 self = [super initWithFrame:frame];
24 if (self) {
25 UIImageView *imageView=[[UIImageView alloc]init];
26 [self addSubview:imageView];
27 self.imageView=imageView;
28
29 UILabel *priceLabel=[[UILabel alloc]init];
30 priceLabel.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3];
31 priceLabel.textAlignment=NSTextAlignmentCenter;
32 priceLabel.textColor=[UIColor whiteColor];
33 [self addSubview:priceLabel];
34 self.priceLabel=priceLabel;
35
36 }
37 return self;
38 }
39
40 +(instancetype)cellWithwaterflowView:(YYWaterflowView *)waterflowView
41 {
42 static NSString *ID=@"ID";
43 YYShopCell *cell=[waterflowView dequeueReusableCellWithIdentifier:ID];
44 if (cell==nil) {
45 cell=[[YYShopCell alloc]init];
46 cell.identifier=ID;
47 }
48 return cell;
49 }
50
51 -(void)setShop:(YYShop *)shop
52 {
53 _shop=shop;
54 self.priceLabel.text=shop.price;
55 [self.imageView sd_setImageWithURL:[NSURL URLWithString:shop.img] placeholderImage:[UIImage imageNamed:@"loading"]];
56 }
57
58 -(void)layoutSubviews
59 {
60 [super layoutSubviews];
61
62 self.imageView.frame=self.bounds;
63
64 CGFloat priceX=0;
65 CGFloat priceH=25;
66 CGFloat priceY=self.bounds.size.height-priceH;
67 CGFloat priceW=self.bounds.size.width;
68
69 self.priceLabel.frame=CGRectMake(priceX, priceY, priceW, priceH);
70 }
71 @end
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

2.代码说明

该项目中使用了第三方框架如下:分别用来处理字典转模型,下载网络图片。

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)

在pch文件中对随机色的处理代码:

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
 1 //
2 // Prefix header
3 //
4 // The contents of this file are implicitly included at the beginning of every source file.
5 //
6
7 #import <Availability.h>
8
9 #ifndef __IPHONE_5_0
10 #warning "This project uses features only available in iOS SDK 5.0 and later."
11 #endif
12
13 #ifdef __OBJC__
14 #import <UIKit/UIKit.h>
15 #import <Foundation/Foundation.h>
16
17 // 颜色
18 #define YYColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]
19 #define YYColorRGBA(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]
20
21 // 随机色
22 #define YYRandomColor YYColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256))
23 #endif


说明:已经实现了cell的循环利用。