iOS开发UI篇—自定义瀑布流控件(接口设计)
一、简单说明
1.关于瀑布流
电商应用要展示商品信息通常是通过瀑布流的方式,因为每个商品的展示图片,长度和商都都不太一样。
如果不用瀑布流的话,展示这样的格子数据,还有一种办法是使用九宫格。
但利用九宫格有一个缺点,那就是每个格子的宽高是一样的,如果一定要使用九宫格来展示,那么展示的商品图片可能会变形。
为了保证商品图片能够按照原来的宽高比进行展示,一般采用的是瀑布流的方式。
2.瀑布流的特点:
由很多的格子组成,但是每个格子的宽度和高速都是不确定的,是在动态改变的,就像瀑布一样,是一条线一条线的。
说明:使用tableView不能实现瀑布流式的布局,因为tableView是以行为单位的,它要求每行(cell)的高度在内部是一致的。
本系列文章介绍了如何自定义一个瀑布流控件来展示商品信息,本文介绍自定义瀑布流的接口设计。
3.自定义瀑布流控件的实现思路
参考UITbaleView控件的设计。
(1)设置数据源(强制的,可选的)
1)告诉有多少个数据(cell)
2)每一个索引对应的cell
3)告诉显示多少列
(2)设置代理
代理方法都是可选的。
1)设置第index位置对应的高度
2)监听选中了第index的cell(控件)
3)设计返回的间距那么
二、自定义瀑布流控件(接口设计)
1.新建一个项目
2.新建一个类,继承自UIScrollView,自己写一个瀑布流控件。
3.接口设计
1 //
2 // YYWaterflowView.h
3 // 06-瀑布流01接口设计
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 @end
主控制器中的使用:
YYViewController.m文件的代码
1 //
2 // YYViewController.m
3 // 06-瀑布流01接口设计
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 #pragma mark-数据源方法
30 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView
31 {
32 return 100;
33 }
34 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView
35 {
36 return 3;
37 }
38 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
39 {
40 YYWaterflowViewCell *cell=[[YYWaterflowViewCell alloc]init];
41 //给cell设置一个随机色
42 cell.backgroundColor=YYRandomColor;
43 return cell;
44 }
45
46
47 #pragma mark-代理方法
48 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index
49 {
50 switch (index%3) {
51 case 0:return 70;
52 case 1:return 100;
53 case 2:return 80;
54 default:return 120;
55 }
56 }
57 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type
58 {
59 switch (type) {
60 case YYWaterflowViewMarginTypeTop:
61 case YYWaterflowViewMarginTypeBottom:
62 case YYWaterflowViewMarginTypeLeft:
63 case YYWaterflowViewMarginTypeRight:
64 return 10;
65 case YYWaterflowViewMarginTypeColumn:
66 case YYWaterflowViewMarginTypeRow:
67 return 20;
68 }
69 }
70 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index
71 {
72 NSLog(@"点击了%d的cell",index);
73 }
74 @end
pch文件中随机色的设置
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
=======================================================================
iOS开发UI篇—自定义瀑布流控件(基本实现)
一、基本实现
说明:在View加载的时候,刷新数据。
1.实现代码
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 100;
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 //给cell设置一个随机色
45 cell.backgroundColor=YYRandomColor;
46 return cell;
47 }
48
49
50 #pragma mark-代理方法
51 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index
52 {
53 switch (index%3) {
54 case 0:return 90;
55 case 1:return 110;
56 case 2:return 80;
57 default:return 120;
58 }
59 }
60 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type
61 {
62 switch (type) {
63 case YYWaterflowViewMarginTypeTop:
64 case YYWaterflowViewMarginTypeBottom:
65 case YYWaterflowViewMarginTypeLeft:
66 case YYWaterflowViewMarginTypeRight:
67 return 10;
68 case YYWaterflowViewMarginTypeColumn:
69 case YYWaterflowViewMarginTypeRow:
70 return 5;
71 }
72 }
73 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index
74 {
75 NSLog(@"点击了%d的cell",index);
76 }
77 @end
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 /**
84 * 刷新数据
85 */
86 -(void)reloadData;
87 @end
瀑布流的内部实现(计算每个cell的frame)
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 @property(nonatomic,strong)NSMutableArray *cellFrames;
17 @end
18
19 @implementation YYWaterflowView
20
21 #pragma mark-懒加载
22 -(NSMutableArray *)cellFrames
23 {
24 if (_cellFrames==nil) {
25 _cellFrames=[NSMutableArray array];
26 }
27 return _cellFrames;
28 }
29
30 - (id)initWithFrame:(CGRect)frame
31 {
32 self = [super initWithFrame:frame];
33 if (self) {
34 }
35 return self;
36 }
37
38 /**
39 * 刷新数据
40 * 1.计算每个cell的frame
41 */
42 -(void)reloadData
43 {
44 //cell的总数是多少
45 int numberOfCells=[self.dadaSource numberOfCellsInWaterflowView:self];
46
47 //cell的列数
48 int numberOfColumns=[self numberOfColumns];
49
50 //间距
51 CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft];
52 CGFloat rightM=[self marginForType:YYWaterflowViewMarginTypeRight];
53 CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn];
54 CGFloat topM=[self marginForType:YYWaterflowViewMarginTypeTop];
55 CGFloat rowM=[self marginForType:YYWaterflowViewMarginTypeRow];
56 CGFloat bottomM=[self marginForType:YYWaterflowViewMarginTypeBottom];
57
58 //(1)cell的宽度
59 //cell的宽度=(整个view的宽度-左边的间距-右边的间距-(列数-1)X每列之间的间距)/总列数
60 CGFloat cellW=(self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns;
61
62
63
64 //用一个C语言的数组来存放所有列的最大的Y值
65 CGFloat maxYOfColumns[numberOfColumns];
66 for (int i=0; i<numberOfColumns; i++) {
67 //初始化数组的数值全部为0
68 maxYOfColumns[i]=0.0;
69 }
70
71
72 //计算每个cell的fram
73 for (int i=0; i<numberOfCells; i++) {
74
75 //(2)cell的高度
76 //询问代理i位置的高度
77 CGFloat cellH=[self heightAtIndex:i];
78
79 //cell处在第几列(最短的一列)
80 NSUInteger cellAtColumn=0;
81
82 //cell所处那列的最大的Y值(当前最短的那一列的最大的Y值)
83 //默认设置最短的一列为第一列(优化性能)
84 CGFloat maxYOfCellAtColumn=maxYOfColumns[cellAtColumn];
85
86 //求出最短的那一列
87 for (int j=0; j<numberOfColumns; j++) {
88 if (maxYOfColumns[j]<maxYOfCellAtColumn) {
89 cellAtColumn=j;
90 maxYOfCellAtColumn=maxYOfColumns[j];
91 }
92 }
93
94 //(3)cell的位置(X,Y)
95 //cell的X=左边的间距+列号*(cell的宽度+每列之间的间距)
96 CGFloat cellX=leftM+cellAtColumn*(cellW +columnM);
97 //cell的Y,先设定为0
98 CGFloat cellY=0;
99 if (maxYOfCellAtColumn==0.0) {//首行
100 cellY=topM;
101 }else
102 {
103 cellY=maxYOfCellAtColumn+rowM;
104 }
105
106 //(4)设置cell的frame并添加到数组中
107 CGRect cellFrame=CGRectMake(cellX, cellY, cellW, cellH);
108 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
109
110 //更新最短那一列的最大的Y值
111 maxYOfColumns[cellAtColumn]=CGRectGetMaxY(cellFrame);
112
113 //显示cell
114 YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];
115 cell.frame=cellFrame;
116 [self addSubview:cell];
117 }
118
119 //设置contentSize
120 CGFloat contentH=maxYOfColumns[0];
121 for (int i=1; i<numberOfColumns; i++) {
122 if (maxYOfColumns[i]>contentH) {
123 contentH=maxYOfColumns[i];
124 }
125 }
126 contentH += bottomM;
127 self.contentSize=CGSizeMake(0, contentH);
128 }
129
130 #pragma mark-私有方法
131 -(CGFloat)marginForType:(YYWaterflowViewMarginType)type
132 {
133 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
134 return [self.delegate waterflowView:self marginForType:type];
135 }else
136 {
137 return YYWaterflowViewDefaultMargin;
138 }
139 }
140
141 -(NSUInteger)numberOfColumns
142 {
143 if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
144 return [self.dadaSource numberOfColumnsInWaterflowView:self];
145 }else
146 {
147 return YYWaterflowViewDefaultNumberOfClunms;
148 }
149 }
150
151 -(CGFloat)heightAtIndex:(NSUInteger)index
152 {
153 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
154 return [self.delegate waterflowView:self heightAtIndex:index];
155 }else
156 {
157 return YYWaterflowViewDefaultCellH;
158 }
159 }
160 @end
实现的瀑布流效果:
2.简单说明
说明:
(1) 瀑布流每一个的宽度是一样的,都是高度不一样
(2) 补齐算法,哪里比较短就补哪里,不是简单的从左到右排(两列之间的差距越来越大)。
这就要求我们时刻知道每一列最大的Y值是多少,以比较哪里“最短”。
可以考虑使用一个C语言的数组来存放所有列的最大Y值
注意数组的初始化操作。
提示:瀑布流的最后一行一般都是参差不齐的。
可扩展性:
简单的修改cell的列数,即可修改布局。
(1)设置瀑布流为2列。
(2)设置瀑布流的列数为4列
(3)如果不设置列数,那么显示的列数默认为3列。
(4)如果不设置高度,那么显示的cell的高度为默认的高度,都是一样的。
(5)cell的上下左右,行和列之间的间距也可以进行调整,这里不做演示。
(6)在cell中可以添加自定义的控件,如Button、imageView等,此时可以向使用UITableView和UITableViewcell一样来使用YYWaterflowView和YYWaterflowViewCell。
3.存在的问题
上面的代码对cell的处理存在很大的性能问题,如果程序中又2000个cell,那么这里就创建了两千个cell,性能很差。
可以通过在layoutSubviews方法中打印查看。
说明:之所以为2002,是因为创建了2000个cell+2个滚动条(水平方向上的滚动条被隐藏了,但是仍然存在)
优化思路:放入到缓存池
=======================================================================
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的创建数量:
=========================================================
iOS开发UI篇—自定义瀑布流控件(cell的事件处理)
一、关于cell的复用的补充
在设置每个索引位置对应的cell的方法中,打印cell的索引和地址,已查看cell的循环利用情况
1 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
2 {
3 // YYWaterflowViewCell *cell=[[YYWaterflowViewCell alloc]init];
4
5 static NSString *ID=@"cell";
6 YYWaterflowViewCell *cell=[waterflowView dequeueReusableCellWithIdentifier:ID];
7 if (cell==nil) {
8 cell=[[YYWaterflowViewCell alloc]init];
9 cell.identifier=ID;
10 //给cell设置一个随机色
11 cell.backgroundColor=YYRandomColor;
12 // [cell addSubview:[UIButton buttonWithType:UIButtonTypeContactAdd]];
13 }
14 //通过取出cell中的label,重新为label的text赋值
15
16 NSLog(@"%d---%p",index,cell);
17 return cell;
18 }
查看:
二、事件处理
1.在cell创建的时候添加一个label,设置label的tag值,以便在之后能够获取到label。
代码示例:
YYViewController.m文件
1 // YYViewController.m
2 // 06-瀑布流
3 //
4 // Created by apple on 14-7-28.
5 // Copyright (c) 2014年 wendingding. All rights reserved.
6 //
7
8 #import "YYViewController.h"
9 #import "YYWaterflowView.h"
10 #import "YYWaterflowViewCell.h"
11
12 @interface YYViewController ()<YYWaterflowViewDelegate,YYWaterflowViewDataSource>
13
14 @end
15
16 @implementation YYViewController
17
18 - (void)viewDidLoad
19 {
20 [super viewDidLoad];
21 YYWaterflowView *waterflow=[[YYWaterflowView alloc]init];
22 waterflow.frame=self.view.bounds;
23 waterflow.delegate=self;
24 waterflow.dadaSource=self;
25 [self.view addSubview:waterflow];
26
27 //刷新数据
28 [waterflow reloadData];
29 }
30
31 #pragma mark-数据源方法
32 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView
33 {
34 return 40;
35 }
36 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView
37 {
38 return 3;
39 }
40 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
41 {
42 // YYWaterflowViewCell *cell=[[YYWaterflowViewCell alloc]init];
43
44 static NSString *ID=@"cell";
45 YYWaterflowViewCell *cell=[waterflowView dequeueReusableCellWithIdentifier:ID];
46 if (cell==nil) {
47 cell=[[YYWaterflowViewCell alloc]init];
48 cell.identifier=ID;
49 //给cell设置一个随机色
50 cell.backgroundColor=YYRandomColor;
51 // [cell addSubview:[UIButton buttonWithType:UIButtonTypeContactAdd]];
52 //在cell中添加一个label,设置tag值
53 UILabel *label=[[UILabel alloc]init];
54 label.tag=10;
55 //注意:需要设置控件的frame值,否则不会显示
56 label.frame=CGRectMake(0, 0, 20, 20);
57 [cell addSubview:label];
58 }
59 //通过取出cell中的label,重新为label的text赋值
60 UILabel *label=(UILabel *)[cell viewWithTag:10];
61 label.text=[NSString stringWithFormat:@"%d",index];
62
63 NSLog(@"%d---%p",index,cell);
64 return cell;
65 }
66
67
68 #pragma mark-代理方法
69 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index
70 {
71 switch (index%3) {
72 case 0:return 90;
73 case 1:return 110;
74 case 2:return 80;
75 default:return 120;
76 }
77 }
78 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type
79 {
80 switch (type) {
81 case YYWaterflowViewMarginTypeTop:
82 case YYWaterflowViewMarginTypeBottom:
83 case YYWaterflowViewMarginTypeLeft:
84 case YYWaterflowViewMarginTypeRight:
85 return 10;
86 case YYWaterflowViewMarginTypeColumn:
87 case YYWaterflowViewMarginTypeRow:
88 return 5;
89 }
90 }
91 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index
92 {
93 NSLog(@"点击了第%d个cell",index);
94 }
95 @end
2.在YYWaterflowView.m中,监听手指对瀑布流的触碰,获得手指在屏幕上点击的触摸点,判断该触摸点是否在cell上,在哪个cell上?
如果点击了cell调用代理方法,那么处理对应cell的点击事件。
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 #pragma mark-公共方法
66 /**
67 * 刷新数据
68 * 1.计算每个cell的frame
69 */
70 -(void)reloadData
71 {
72 //cell的总数是多少
73 int numberOfCells=[self.dadaSource numberOfCellsInWaterflowView:self];
74
75 //cell的列数
76 int numberOfColumns=[self numberOfColumns];
77
78 //间距
79 CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft];
80 CGFloat rightM=[self marginForType:YYWaterflowViewMarginTypeRight];
81 CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn];
82 CGFloat topM=[self marginForType:YYWaterflowViewMarginTypeTop];
83 CGFloat rowM=[self marginForType:YYWaterflowViewMarginTypeRow];
84 CGFloat bottomM=[self marginForType:YYWaterflowViewMarginTypeBottom];
85
86 //(1)cell的宽度
87 //cell的宽度=(整个view的宽度-左边的间距-右边的间距-(列数-1)X每列之间的间距)/总列数
88 CGFloat cellW=(self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns;
89
90
91
92 //用一个C语言的数组来存放所有列的最大的Y值
93 CGFloat maxYOfColumns[numberOfColumns];
94 for (int i=0; i<numberOfColumns; i++) {
95 //初始化数组的数值全部为0
96 maxYOfColumns[i]=0.0;
97 }
98
99
100 //计算每个cell的fram
101 for (int i=0; i<numberOfCells; i++) {
102
103 //(2)cell的高度
104 //询问代理i位置的高度
105 CGFloat cellH=[self heightAtIndex:i];
106
107 //cell处在第几列(最短的一列)
108 NSUInteger cellAtColumn=0;
109
110 //cell所处那列的最大的Y值(当前最短的那一列的最大的Y值)
111 //默认设置最短的一列为第一列(优化性能)
112 CGFloat maxYOfCellAtColumn=maxYOfColumns[cellAtColumn];
113
114 //求出最短的那一列
115 for (int j=0; j<numberOfColumns; j++) {
116 if (maxYOfColumns[j]<maxYOfCellAtColumn) {
117 cellAtColumn=j;
118 maxYOfCellAtColumn=maxYOfColumns[j];
119 }
120 }
121
122 //(3)cell的位置(X,Y)
123 //cell的X=左边的间距+列号*(cell的宽度+每列之间的间距)
124 CGFloat cellX=leftM+cellAtColumn*(cellW +columnM);
125 //cell的Y,先设定为0
126 CGFloat cellY=0;
127 if (maxYOfCellAtColumn==0.0) {//首行
128 cellY=topM;
129 }else
130 {
131 cellY=maxYOfCellAtColumn+rowM;
132 }
133
134 //设置cell的frame并添加到数组中
135 CGRect cellFrame=CGRectMake(cellX, cellY, cellW, cellH);
136 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
137
138 //更新最短那一列的最大的Y值
139 maxYOfColumns[cellAtColumn]=CGRectGetMaxY(cellFrame);
140
141 //显示cell
142 // YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];
143 // cell.frame=cellFrame;
144 // [self addSubview:cell];
145 }
146
147 //设置contentSize
148 CGFloat contentH=maxYOfColumns[0];
149 for (int i=1; i<numberOfColumns; i++) {
150 if (maxYOfColumns[i]>contentH) {
151 contentH=maxYOfColumns[i];
152 }
153 }
154 contentH += bottomM;
155 self.contentSize=CGSizeMake(0, contentH);
156 }
157
158 /**
159 * 当UIScrollView滚动的时候也会调用这个方法
160 */
161 -(void)layoutSubviews
162 {
163 [super layoutSubviews];
164
165
166 //向数据源索要对应位置的cell
167 NSUInteger numberOfCells=self.cellFrames.count;
168 for (int i=0; i<numberOfCells; i++) {
169 //取出i位置的frame,注意转换
170 CGRect cellFrame=[self.cellFrames[i] CGRectValue];
171
172 //优先从字典中取出i位置的cell
173 YYWaterflowViewCell *cell=self.displayingCells[@(i)];
174
175 //判断i位置对应的frame在不在屏幕上(能否看见)
176 if ([self isInScreen:cellFrame]) {//在屏幕上
177 if (cell==nil) {
178 cell= [self.dadaSource waterflowView:self cellAtIndex:i];
179 cell.frame=cellFrame;
180 [self addSubview:cell];
181
182 //存放在字典中
183 self.displayingCells[@(i)]=cell;
184 }
185
186 }else //不在屏幕上
187 {
188 if (cell) {
189 //从scrollView和字典中删除
190 [cell removeFromSuperview];
191 [self.displayingCells removeObjectForKey:@(i)];
192
193 //存放进缓存池
194 [self.reusableCells addObject:cell];
195 }
196 }
197 }
198 // NSLog(@"%d",self.subviews.count);
199 }
200
201 -(id)dequeueReusableCellWithIdentifier:(NSString *)identifier
202 {
203 __block YYWaterflowViewCell *reusableCell=nil;
204 [self.reusableCells enumerateObjectsUsingBlock:^(YYWaterflowViewCell *cell, BOOL *stop) {
205 if ([cell.identifier isEqualToString:identifier]) {
206 reusableCell=cell;
207 *stop=YES;
208 }
209 }];
210
211 if (reusableCell) {//从缓存池中移除(已经用掉了)
212 [self.reusableCells removeObject:reusableCell];
213 }
214 return reusableCell;
215 }
216
217 #pragma mark cell的事件处理
218 -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
219 {
220 //如果没有点击事件的代理方法,那么就直接返回
221 if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)])
222 return;
223
224 //获得手指在屏幕上点击的触摸点
225 UITouch *touch=[touches anyObject];
226 // CGPoint point=[touch locationInView:touch.view];
227 CGPoint point=[touch locationInView:self];
228
229 __block NSNumber *selectIndex=nil;
230 [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, YYWaterflowViewCell *cell, BOOL *stop) {
231 if (CGRectContainsPoint(cell.frame, point)) {
232 selectIndex=key;
233 *stop=YES;
234 }
235 }];
236 if (selectIndex) {
237 //需要转换
238 [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue];
239 }
240
241 }
242 #pragma mark-私有方法
243 /**
244 * 判断一个人cell的frame有没有显示在屏幕上
245 */
246 -(BOOL)isInScreen:(CGRect)frame
247 {
248 // return (CGRectGetMaxY(frame)>self.contentOffset.y)&&(CGRectGetMaxY(frame)<self.contentOffset.y+self.frame.size.height);
249 return (CGRectGetMaxY(frame) > self.contentOffset.y) &&
250 (CGRectGetMinY(frame) < self.contentOffset.y + self.frame.size.height);
251
252 }
253 -(CGFloat)marginForType:(YYWaterflowViewMarginType)type
254 {
255 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
256 return [self.delegate waterflowView:self marginForType:type];
257 }else
258 {
259 return YYWaterflowViewDefaultMargin;
260 }
261 }
262
263 -(NSUInteger)numberOfColumns
264 {
265 if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
266 return [self.dadaSource numberOfColumnsInWaterflowView:self];
267 }else
268 {
269 return YYWaterflowViewDefaultNumberOfClunms;
270 }
271 }
272
273 -(CGFloat)heightAtIndex:(NSUInteger)index
274 {
275 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
276 return [self.delegate waterflowView:self heightAtIndex:index];
277 }else
278 {
279 return YYWaterflowViewDefaultCellH;
280 }
281 }
282 @end
实现效果:
点击对应的cell,能够监听并对点击事件进行处理。
三、补充说明
示例代码:
说明:
touch.view指的是子控件(单个cell)以这个cell的左上角为(0,0)进行坐标计算
self指的是整个瀑布流,以整个瀑布流的左上角为(0,0)进行坐标计算
打印查看:
iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流)
一、简单说明
关于瀑布流
1.是使用UIScrollView实现的
2.刷新数据(reloadData)方法里面做哪些事情
3.layoutSubviews方法里面做哪些事情
4.模仿UItableView进行设计
完善:
瀑布流控件第一次显示到屏幕上的时候自动的向数据源索要数据,而不需要手动调用。这需要监听View的显示,View的显示有一个方法,叫做willMoveToSuperview:在该方法中直接刷新一次数据即可。
二、把自定义的瀑布流控件作为一个框架进行使用
1.框架
把自定义的瀑布流控件转变成一个框架。以后可以把它作为一套框架进行开发。
2.瀑布流框架的使用
在做瀑布流应用的时候,一定要和服务器开发人员沟通清楚,提供的数据一定要包括图片的宽度和高度,否则没有办法进行处理。
为了保证图片不会变形,因此不能直接返回图片的宽度或者是高度,而应该使用宽高的比值。应该根据cell的宽度,有图片的宽高比计算出图片的高度(正式的高度!=真实的高度 cellW/cellH=真实的宽度/真实的高度==>cellH=cellW*真实的高度/真实的宽度)。
需要在框架中提供一个接口,获取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 *cell的宽度
86 */
87 -(CGFloat)cellWidth;
88 /**
89 * 刷新数据
90 */
91 -(void)reloadData;
92 /**
93 * 根据标识去缓存池中查找可循环利用的cell
94 */
95 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
96 @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 -(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
提示:瀑布流有一个特点,即每个cell的宽度都是一样的。
三、蘑菇街的实现
1.新建一个项目,使用自定义的瀑布流框架
观察plist文件的数据结构
新建一个shop模型,继承自NSObject类,
该类中的代码设计如下:
YYShop.h文件
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
控制器中的代码设计和处理:
YYShopViewController.m文件
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
对瀑布流的cell进行自定义,按照需要的方式处理cell中的子控件(这里包括imageView和label控件)
自定义cell的代码处理如下:
YYShopCell.h文件
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
YYShopCell.m文件
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
2.代码说明
该项目中使用了第三方框架如下:分别用来处理字典转模型,下载网络图片。
在pch文件中对随机色的处理代码:
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
3.运行效果
说明:已经实现了cell的循环利用。
iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作)
一、简单说明
使用数据刷新框架:
该框架提供了两种刷新的方法,一个是使用block回调(存在循环引用问题,_ _weak),一个是使用调用。
问题:在进行下拉刷新之前,应该要清空之前的所有数据(在刷新数据这个方法中)。
移除正在显示的cell:
(1)把字典中的所有的值,都从屏幕上移除
(2)清除字典中的所有元素
(3)清除cell的frame,每个位置的cell的frame都要重新计算
(4)清除可复用的缓存池。
该部分的代码如下:
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 /*
91 (1)把字典中的所有的值,都从屏幕上移除
92 (2)清除字典中的所有元素
93 (3)清除cell的frame,每个位置的cell的frame都要重新计算
94 (4)清除可复用的缓存池。
95 */
96
97 [self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)];
98 [self.displayingCells removeAllObjects];
99 [self.cellFrames removeAllObjects];
100 [self.reusableCells removeAllObjects];
101
102 //cell的总数是多少
103 int numberOfCells=[self.dadaSource numberOfCellsInWaterflowView:self];
104
105 //cell的列数
106 int numberOfColumns=[self numberOfColumns];
107
108 //间距
109 CGFloat leftM=[self marginForType:YYWaterflowViewMarginTypeLeft];
110
111 CGFloat columnM=[self marginForType:YYWaterflowViewMarginTypeColumn];
112 CGFloat topM=[self marginForType:YYWaterflowViewMarginTypeTop];
113 CGFloat rowM=[self marginForType:YYWaterflowViewMarginTypeRow];
114 CGFloat bottomM=[self marginForType:YYWaterflowViewMarginTypeBottom];
115
116 //(1)cell的宽度
117 //cell的宽度=(整个view的宽度-左边的间距-右边的间距-(列数-1)X每列之间的间距)/总列数
118 // CGFloat cellW=(self.frame.size.width-leftM-rightM-(numberOfColumns-1)*columnM)/numberOfColumns;
119 CGFloat cellW=[self cellWidth];
120
121 //用一个C语言的数组来存放所有列的最大的Y值
122 CGFloat maxYOfColumns[numberOfColumns];
123 for (int i=0; i<numberOfColumns; i++) {
124 //初始化数组的数值全部为0
125 maxYOfColumns[i]=0.0;
126 }
127
128
129 //计算每个cell的fram
130 for (int i=0; i<numberOfCells; i++) {
131
132 //(2)cell的高度
133 //询问代理i位置的高度
134 CGFloat cellH=[self heightAtIndex:i];
135
136 //cell处在第几列(最短的一列)
137 NSUInteger cellAtColumn=0;
138
139 //cell所处那列的最大的Y值(当前最短的那一列的最大的Y值)
140 //默认设置最短的一列为第一列(优化性能)
141 CGFloat maxYOfCellAtColumn=maxYOfColumns[cellAtColumn];
142
143 //求出最短的那一列
144 for (int j=0; j<numberOfColumns; j++) {
145 if (maxYOfColumns[j]<maxYOfCellAtColumn) {
146 cellAtColumn=j;
147 maxYOfCellAtColumn=maxYOfColumns[j];
148 }
149 }
150
151 //(3)cell的位置(X,Y)
152 //cell的X=左边的间距+列号*(cell的宽度+每列之间的间距)
153 CGFloat cellX=leftM+cellAtColumn*(cellW +columnM);
154 //cell的Y,先设定为0
155 CGFloat cellY=0;
156 if (maxYOfCellAtColumn==0.0) {//首行
157 cellY=topM;
158 }else
159 {
160 cellY=maxYOfCellAtColumn+rowM;
161 }
162
163 //设置cell的frame并添加到数组中
164 CGRect cellFrame=CGRectMake(cellX, cellY, cellW, cellH);
165 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
166
167 //更新最短那一列的最大的Y值
168 maxYOfColumns[cellAtColumn]=CGRectGetMaxY(cellFrame);
169 }
170
171 //设置contentSize
172 CGFloat contentH=maxYOfColumns[0];
173 for (int i=1; i<numberOfColumns; i++) {
174 if (maxYOfColumns[i]>contentH) {
175 contentH=maxYOfColumns[i];
176 }
177 }
178 contentH += bottomM;
179 self.contentSize=CGSizeMake(0, contentH);
180 }
181
182 /**
183 * 当UIScrollView滚动的时候也会调用这个方法
184 */
185 -(void)layoutSubviews
186 {
187 [super layoutSubviews];
188
189
190 //向数据源索要对应位置的cell
191 NSUInteger numberOfCells=self.cellFrames.count;
192 for (int i=0; i<numberOfCells; i++) {
193 //取出i位置的frame,注意转换
194 CGRect cellFrame=[self.cellFrames[i] CGRectValue];
195
196 //优先从字典中取出i位置的cell
197 YYWaterflowViewCell *cell=self.displayingCells[@(i)];
198
199 //判断i位置对应的frame在不在屏幕上(能否看见)
200 if ([self isInScreen:cellFrame]) {//在屏幕上
201 if (cell==nil) {
202 cell= [self.dadaSource waterflowView:self cellAtIndex:i];
203 cell.frame=cellFrame;
204 [self addSubview:cell];
205
206 //存放在字典中
207 self.displayingCells[@(i)]=cell;
208 }
209
210 }else //不在屏幕上
211 {
212 if (cell) {
213 //从scrollView和字典中删除
214 [cell removeFromSuperview];
215 [self.displayingCells removeObjectForKey:@(i)];
216
217 //存放进缓存池
218 [self.reusableCells addObject:cell];
219 }
220 }
221 }
222 // NSLog(@"%d",self.subviews.count);
223 }
224
225 -(id)dequeueReusableCellWithIdentifier:(NSString *)identifier
226 {
227 __block YYWaterflowViewCell *reusableCell=nil;
228 [self.reusableCells enumerateObjectsUsingBlock:^(YYWaterflowViewCell *cell, BOOL *stop) {
229 if ([cell.identifier isEqualToString:identifier]) {
230 reusableCell=cell;
231 *stop=YES;
232 }
233 }];
234
235 if (reusableCell) {//从缓存池中移除(已经用掉了)
236 [self.reusableCells removeObject:reusableCell];
237 }
238 return reusableCell;
239 }
240
241 #pragma mark cell的事件处理
242 -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
243 {
244 //如果没有点击事件的代理方法,那么就直接返回
245 if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)])
246 return;
247
248 //获得手指在屏幕上点击的触摸点
249 UITouch *touch=[touches anyObject];
250 CGPoint point1=[touch locationInView:touch.view];
251 CGPoint point=[touch locationInView:self];
252 NSLog(@"%@--%@",NSStringFromCGPoint(point),NSStringFromCGPoint(point1));
253
254 __block NSNumber *selectIndex=nil;
255 [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, YYWaterflowViewCell *cell, BOOL *stop) {
256 if (CGRectContainsPoint(cell.frame, point)) {
257 selectIndex=key;
258 *stop=YES;
259 }
260 }];
261 if (selectIndex) {
262 //需要转换
263 [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue];
264 }
265
266 }
267 #pragma mark-私有方法
268 /**
269 * 判断一个人cell的frame有没有显示在屏幕上
270 */
271 -(BOOL)isInScreen:(CGRect)frame
272 {
273 // return (CGRectGetMaxY(frame)>self.contentOffset.y)&&(CGRectGetMaxY(frame)<self.contentOffset.y+self.frame.size.height);
274 return (CGRectGetMaxY(frame) > self.contentOffset.y) &&
275 (CGRectGetMinY(frame) < self.contentOffset.y + self.frame.size.height);
276
277 }
278 -(CGFloat)marginForType:(YYWaterflowViewMarginType)type
279 {
280 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
281 return [self.delegate waterflowView:self marginForType:type];
282 }else
283 {
284 return YYWaterflowViewDefaultMargin;
285 }
286 }
287
288 -(NSUInteger)numberOfColumns
289 {
290 if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
291 return [self.dadaSource numberOfColumnsInWaterflowView:self];
292 }else
293 {
294 return YYWaterflowViewDefaultNumberOfClunms;
295 }
296 }
297
298 -(CGFloat)heightAtIndex:(NSUInteger)index
299 {
300 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
301 return [self.delegate waterflowView:self heightAtIndex:index];
302 }else
303 {
304 return YYWaterflowViewDefaultCellH;
305 }
306 }
307 @end
二、刷新操作
刷新操作的代码设计:
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 #import "MJRefresh.h"
16
17 @interface YYShopViewController ()<YYWaterflowViewDataSource,YYWaterflowViewDelegate>
18 @property(nonatomic,strong)NSMutableArray *shops;
19 @property(nonatomic,strong)YYWaterflowView *waterflowView;
20 @end
21
22 @implementation YYShopViewController
23
24 #pragma mark-懒加载
25 -(NSMutableArray *)shops
26 {
27 if (_shops==nil) {
28 _shops=[NSMutableArray array];
29 }
30 return _shops;
31 }
32 - (void)viewDidLoad
33 {
34 [super viewDidLoad];
35
36 //1.初始化数据
37 NSArray *newShop=[YYShop objectArrayWithFilename:@"2.plist"];
38 [self.shops addObjectsFromArray:newShop];
39
40 //2.创建一个瀑布流
41 YYWaterflowView *waterflow=[[YYWaterflowView alloc]init];
42 waterflow.autoresizingMask=UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
43 waterflow.frame=self.view.bounds;
44 waterflow.delegate=self;
45 waterflow.dadaSource=self;
46 [self.view addSubview:waterflow];
47
48 self.waterflowView=waterflow;
49
50 //3.实现数据的刷新
51 // [waterflow addFooterWithCallback:^{
52 // NSLog(@"上拉数据刷新");
53 // }];
54 //
55 // [waterflow addHeaderWithCallback:^{
56 // NSLog(@"下拉数据刷新");
57 // }];
58
59 [waterflow addHeaderWithTarget:self action:@selector(loadNewShops)];
60 [waterflow addFooterWithTarget:self action:@selector(loadMoreShops)];
61 }
62
63 -(void)loadNewShops
64 {
65 //模拟,只执行一次刷新操作
66 static dispatch_once_t onceToken;
67 dispatch_once(&onceToken, ^{
68 //加载1.plist文件
69 NSArray *newShop=[YYShop objectArrayWithFilename:@"1.plist"];
70 [self.shops insertObjects:newShop atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newShop.count)]];
71 });
72
73 //模拟网络延迟,2.0秒钟之后执行
74 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
75 //刷新数据
76 [self.waterflowView reloadData];
77
78 //停止刷新
79 [self.waterflowView headerEndRefreshing];
80 });
81 }
82
83 -(void)loadMoreShops
84 {
85 static dispatch_once_t onceToken;
86 dispatch_once(&onceToken, ^{
87 //加载1.plist文件
88 NSArray *newShop=[YYShop objectArrayWithFilename:@"3.plist"];
89 [self.shops addObjectsFromArray:newShop];
90 });
91
92 //模拟网络延迟,2.0秒钟之后执行
93 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
94 //刷新数据
95 [self.waterflowView reloadData];
96
97 //停止刷新
98 [self.waterflowView footerEndRefreshing];
99 });
100
101 }
102
103 #pragma mark-数据源方法
104 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView
105 {
106 return self.shops.count;
107 }
108 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView
109 {
110 return 3;
111 }
112 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
113 {
114 YYShopCell *cell=[YYShopCell cellWithwaterflowView:waterflowView];
115 cell.shop=self.shops[index];
116 return cell;
117 }
118
119
120 #pragma mark-代理方法
121 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index
122 {
123 YYShop *shop=self.shops[index];
124 //根据Cell的宽度和图片的宽高比 算出cell的高度
125 return waterflowView.cellWidth*shop.h/shop.w;
126 }
127
128 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index
129 {
130 NSLog(@"点击了第%d个cell",index);
131 }
132
133
134 @end
实现的刷新效果:
三、竖屏和横屏调整
设置横屏和竖屏。
屏幕旋转完毕会调用下面的方法。
因为scrollView的宽度是固定的,没有改变。
设置view的宽度和高度可以跟随者父控件自动拉伸。(在iPad开发中会将常用到)