首页 > 代码库 > iOS开发UI篇—自定义瀑布流控件(蘑菇街实现)
iOS开发UI篇—自定义瀑布流控件(蘑菇街实现)
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 @required30 /**31 * (1)一共有多少个数据32 */33 -(NSUInteger)numberOfCellsInWaterflowView:(YYWaterflowView *)waterflowView;34 /**35 * (2)返回index位置对应的cell36 */37 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index;38 39 //不要求强制实现40 @optional41 /**42 * (3)一共有多少列43 */44 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView;45 46 @end47 48 49 /**50 * 2.代理方法51 */52 @protocol YYWaterflowViewDelegate <UIScrollViewDelegate>53 //不要求强制实现54 @optional55 /**56 * (1)第index位置cell对应的高度57 */58 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;59 /**60 * (2)选中第index位置的cell61 */62 -(void)waterflowView:(YYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index;63 /**64 * (3)返回间距65 */66 -(CGFloat)waterflowView:(YYWaterflowView *)waterflowView marginForType:(YYWaterflowViewMarginType)type;67 @end68 69 70 /**71 * 3.瀑布流控件72 */73 @interface YYWaterflowView : UIScrollView74 /**75 * (1)数据源76 */77 @property(nonatomic,weak)id<YYWaterflowViewDataSource> dadaSource;78 /**79 * (2)代理80 */81 @property(nonatomic,weak)id<YYWaterflowViewDelegate> delegate;82 83 #pragma mark-公共方法84 /*85 *cell的宽度86 */87 -(CGFloat)cellWidth;88 /**89 * 刷新数据90 */91 -(void)reloadData;92 /**93 * 根据标识去缓存池中查找可循环利用的cell94 */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 //初始化数组的数值全部为0113 maxYOfColumns[i]=0.0;114 }115 116 117 //计算每个cell的fram118 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,先设定为0143 CGFloat cellY=0;144 if (maxYOfCellAtColumn==0.0) {//首行145 cellY=topM;146 }else147 {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 //显示cell159 // YYWaterflowViewCell *cell=[self.dadaSource waterflowView:self cellAtIndex:i];160 // cell.frame=cellFrame;161 // [self addSubview:cell];162 }163 164 //设置contentSize165 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)layoutSubviews179 {180 [super layoutSubviews];181 182 183 //向数据源索要对应位置的cell184 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位置的cell190 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 *)identifier219 {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 *)event236 {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)frame265 {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)type272 {273 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {274 return [self.delegate waterflowView:self marginForType:type];275 }else276 {277 return YYWaterflowViewDefaultMargin;278 }279 }280 281 -(NSUInteger)numberOfColumns282 {283 if ([self.dadaSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {284 return [self.dadaSource numberOfColumnsInWaterflowView:self];285 }else286 {287 return YYWaterflowViewDefaultNumberOfClunms;288 }289 }290 291 -(CGFloat)heightAtIndex:(NSUInteger)index292 {293 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {294 return [self.delegate waterflowView:self heightAtIndex:index];295 }else296 {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 : NSObject12 /**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 @end19 20 @implementation YYShopViewController21 22 #pragma mark-懒加载23 -(NSMutableArray *)shops24 {25 if (_shops==nil) {26 _shops=[NSMutableArray array];27 }28 return _shops;29 }30 - (void)viewDidLoad31 {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 *)waterflowView46 {47 return 40;48 }49 -(NSUInteger)numberOfColumnsInWaterflowView:(YYWaterflowView *)waterflowView50 {51 return 3;52 }53 -(YYWaterflowViewCell *)waterflowView:(YYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index54 {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)index63 {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)index70 {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 : YYWaterflowViewCell12 @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 @end19 @implementation YYShopCell20 21 - (id)initWithFrame:(CGRect)frame22 {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 *)waterflowView41 {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 *)shop52 {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)layoutSubviews59 {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_010 #warning "This project uses features only available in iOS SDK 5.0 and later."11 #endif12 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的循环利用。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。