ios开发瀑布流框架的封装
一:瀑布流框架封裝的實現思路:此瀑布流框架的封裝仿照tableView的底層實現,1:每個cell的frame的設置都是找出每列的最大y值,比較每列的最大y值,將下一個cell放在最大y值最小的那一列,并更新最大y值,繼續比較設置frame。2:還涉及了類似于tableView緩存池的處理 ?瀑布流效果如圖:
二:封裝代碼:
1:瀑布流控件的view封裝:
1 // 使用瀑布流形式展示內容的控件 2 3 #import <UIKit/UIKit.h> 4 5 typedef enum { 6 HMWaterflowViewMarginTypeTop, 7 HMWaterflowViewMarginTypeBottom, 8 HMWaterflowViewMarginTypeLeft, 9 HMWaterflowViewMarginTypeRight, 10 HMWaterflowViewMarginTypeColumn, // 每一列 11 HMWaterflowViewMarginTypeRow, // 每一行 12 } HMWaterflowViewMarginType; 13 14 @class HMWaterflowView, HMWaterflowViewCell; 15 16 /** 17 * 數據源方法 18 */ 19 @protocol HMWaterflowViewDataSource <NSObject> 20 @required 21 /** 22 * 一共有多少個數據 23 */ 24 - (NSUInteger)numberOfCellsInWaterflowView:(HMWaterflowView *)waterflowView; 25 /** 26 * 返回index位置對應的cell 27 */ 28 - (HMWaterflowViewCell *)waterflowView:(HMWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index; 29 30 @optional 31 /** 32 * 一共有多少列 33 */ 34 - (NSUInteger)numberOfColumnsInWaterflowView:(HMWaterflowView *)waterflowView; 35 @end 36 37 /** 38 * 代理方法 39 */ 40 @protocol HMWaterflowViewDelegate <UIScrollViewDelegate> 41 @optional 42 /** 43 * 第index位置cell對應的高度 44 */ 45 - (CGFloat)waterflowView:(HMWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index; 46 /** 47 * 選中第index位置的cell 48 */ 49 - (void)waterflowView:(HMWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index; 50 /** 51 * 返回間距 52 */ 53 - (CGFloat)waterflowView:(HMWaterflowView *)waterflowView marginForType:(HMWaterflowViewMarginType)type; 54 @end 55 56 /** 57 * 瀑布流控件 58 */ 59 @interface HMWaterflowView : UIScrollView 60 /** 61 * 數據源 62 */ 63 @property (nonatomic, weak) id<HMWaterflowViewDataSource> dataSource; 64 /** 65 * 代理 66 */ 67 @property (nonatomic, weak) id<HMWaterflowViewDelegate> delegate; 68 69 /** 70 * 刷新數據(只要調用這個方法,會重新向數據源和代理發送請求,請求數據) 71 */ 72 - (void)reloadData; 73 74 /** 75 * cell的寬度 76 */ 77 - (CGFloat)cellWidth; 78 79 /** 80 * 根據標識去緩存池查找可循環利用的cell 81 */ 82 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier; 83 @end思路分析:1:仿照tableView來設置dataSource 和 delegate 兩個代理:waterFlow的dataSource代理方法中仿照tableView的dataSource代理方法,@required 必須實現的是,1:一共有多少個cell,- (NSUInteger)numberOfCellsInWaterflowView:(HMWaterflowView *)waterflowView ,2:根據cell的index返回每一個cell,- (HMWaterflowViewCell *)waterflowView:(HMWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index;其中HMWaterflowViewCell就為每一個單元的cell,自定義HMWaterflowViewCell繼承于UIView 3:@optional 方法為,一共返回多少列,如果沒有實現此方法則返回默認的列數 2:在waterFlow的delegate方法中,都為@optional,并仿照tableView提供,1:根據index返回每個cell的行高 ?- (CGFloat)waterflowView:(HMWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;2:點擊index處的cell的方法:- (void)waterflowView:(HMWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index; 3:返回整個瀑布流的間距值,若不返回,則會有默認值,上下左右,行間距,列間距,根據上下左右行列不同的類型返回不同的高度,將這幾個不同的類型定義成枚舉值,將枚舉傳過去,外界可利用switch根據不同類型,返回不同的間距(類似于按鈕回調的枚舉tag)
2:以屬性設置兩個代理:設置delegate時,會有警告,因為瀑布流控件繼承的是UIScroll,則繼承了UIScroll在.h聲明的屬性和方法,此delegate會覆蓋掉scrollView的滾動視圖的代理,所以會有警告,解決辦法是,讓瀑布流控件的代理協議再遵守UISCrollViewDelegate,則在外部只要遵守了瀑布流的代理,也就遵守了UIScroll的滾動代理,所以以屬性設置瀑布流代理后,外部遵守協議,設置代理,則其就可以調用UIscroll的滾動代理方法了
3:仿照tableView并提供刷新表格,和緩存池根據重用標識取cell的方法,- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;其中id 和 instanceType的區別:1:id 修飾非關聯返回類型 ,可做參數,也可以做返回值 而instanceType為關聯返回類型,返回該方法所在類的類型,并且只能做返回值,一般以alloc。autorelease,retain開頭的方法用id 2:用instanceType相對id來說,能幫助編譯器快速確定返回值類型。
2:瀑布流的內部核心封裝代碼:
1 #import "HMWaterflowView.h" 2 #import "HMWaterflowViewCell.h" 3 4 #define HMWaterflowViewDefaultCellH 70 5 #define HMWaterflowViewDefaultMargin 8 6 #define HMWaterflowViewDefaultNumberOfColumns 3 7 8 @interface HMWaterflowView() 9 /** 10 * 所有cell的frame數據 11 */ 12 @property (nonatomic, strong) NSMutableArray *cellFrames; 13 /** 14 * 正在展示的cell 15 */ 16 @property (nonatomic, strong) NSMutableDictionary *displayingCells; 17 /** 18 * 緩存池(用Set,存放離開屏幕的cell) 19 */ 20 @property (nonatomic, strong) NSMutableSet *reusableCells; 21 @end 22 23 @implementation HMWaterflowView 24 25 #pragma mark - 初始化 26 - (NSMutableArray *)cellFrames 27 { 28 if (_cellFrames == nil) { 29 self.cellFrames = [NSMutableArray array]; 30 } 31 return _cellFrames; 32 } 33 34 - (NSMutableDictionary *)displayingCells 35 { 36 if (_displayingCells == nil) { 37 self.displayingCells = [NSMutableDictionary dictionary]; 38 } 39 return _displayingCells; 40 } 41 42 - (NSMutableSet *)reusableCells 43 { 44 if (_reusableCells == nil) { 45 self.reusableCells = [NSMutableSet set]; 46 } 47 return _reusableCells; 48 } 49 50 - (id)initWithFrame:(CGRect)frame 51 { 52 self = [super initWithFrame:frame]; 53 if (self) { 54 55 } 56 return self; 57 } 58 59 - (void)willMoveToSuperview:(UIView *)newSuperview 60 { 61 [self reloadData]; 62 } 63 64 #pragma mark - 公共接口 65 /** 66 * cell的寬度 67 */ 68 - (CGFloat)cellWidth 69 { 70 // 總列數 71 int numberOfColumns = [self numberOfColumns]; 72 CGFloat leftM = [self marginForType:HMWaterflowViewMarginTypeLeft]; 73 CGFloat rightM = [self marginForType:HMWaterflowViewMarginTypeRight]; 74 CGFloat columnM = [self marginForType:HMWaterflowViewMarginTypeColumn]; 75 return (self.bounds.size.width - leftM - rightM - (numberOfColumns - 1) * columnM) / numberOfColumns; 76 } 77 78 /** 79 * 刷新數據 80 */ 81 - (void)reloadData 82 { 83 // 清空之前的所有數據 84 // 移除正在正在顯示cell 85 [self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)]; 86 [self.displayingCells removeAllObjects]; 87 [self.cellFrames removeAllObjects]; 88 [self.reusableCells removeAllObjects]; 89 90 // cell的總數 91 int numberOfCells = [self.dataSource numberOfCellsInWaterflowView:self]; 92 93 // 總列數 94 int numberOfColumns = [self numberOfColumns]; 95 96 // 間距 97 CGFloat topM = [self marginForType:HMWaterflowViewMarginTypeTop]; 98 CGFloat bottomM = [self marginForType:HMWaterflowViewMarginTypeBottom]; 99 CGFloat leftM = [self marginForType:HMWaterflowViewMarginTypeLeft]; 100 CGFloat columnM = [self marginForType:HMWaterflowViewMarginTypeColumn]; 101 CGFloat rowM = [self marginForType:HMWaterflowViewMarginTypeRow]; 102 103 // cell的寬度 104 CGFloat cellW = [self cellWidth]; 105 106 // 用一個C語言數組存放所有列的最大Y值 107 CGFloat maxYOfColumns[numberOfColumns]; 108 for (int i = 0; i<numberOfColumns; i++) { 109 maxYOfColumns[i] = 0.0; 110 } 111 112 // 計算所有cell的frame 113 for (int i = 0; i<numberOfCells; i++) { 114 // cell處在第幾列(最短的一列) 115 NSUInteger cellColumn = 0; 116 // cell所處那列的最大Y值(最短那一列的最大Y值) 117 CGFloat maxYOfCellColumn = maxYOfColumns[cellColumn]; 118 // 求出最短的一列 119 for (int j = 1; j<numberOfColumns; j++) { 120 if (maxYOfColumns[j] < maxYOfCellColumn) { 121 cellColumn = j; 122 maxYOfCellColumn = maxYOfColumns[j]; 123 } 124 } 125 126 // 詢問代理i位置的高度 127 CGFloat cellH = [self heightAtIndex:i]; 128 129 // cell的位置 130 CGFloat cellX = leftM + cellColumn * (cellW + columnM); 131 CGFloat cellY = 0; 132 if (maxYOfCellColumn == 0.0) { // 首行 133 cellY = topM; 134 } else { 135 cellY = maxYOfCellColumn + rowM; 136 } 137 138 // 添加frame到數組中 139 CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH); 140 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]]; 141 142 // 更新最短那一列的最大Y值 143 maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame); 144 } 145 146 // 設置contentSize 147 CGFloat contentH = maxYOfColumns[0]; 148 for (int j = 1; j<numberOfColumns; j++) { 149 if (maxYOfColumns[j] > contentH) { 150 contentH = maxYOfColumns[j]; 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 // 向數據源索要對應位置的cell 165 NSUInteger numberOfCells = self.cellFrames.count; 166 for (int i = 0; i<numberOfCells; i++) { 167 // 取出i位置的frame 168 CGRect cellFrame = [self.cellFrames[i] CGRectValue]; 169 170 // 優先從字典中取出i位置的cell 171 HMWaterflowViewCell *cell = self.displayingCells[@(i)]; 172 173 // 判斷i位置對應的frame在不在屏幕上(能否看見) 174 if ([self isInScreen:cellFrame]) {// 在屏幕上 175 if (cell == nil) { 176 cell = [self.dataSource waterflowView:self cellAtIndex:i]; 177 cell.frame = cellFrame; 178 [self addSubview:cell]; 179 180 // 存放到字典中 181 self.displayingCells[@(i)] = cell; 182 } 183 } else { // 不在屏幕上 184 if (cell) { 185 // 從scrollView和字典中移除 186 [cell removeFromSuperview]; 187 [self.displayingCells removeObjectForKey:@(i)]; 188 189 // 存放進緩存池 190 [self.reusableCells addObject:cell]; 191 } 192 } 193 } 194 } 195 196 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier 197 { 198 __block HMWaterflowViewCell *reusableCell = nil; 199 200 [self.reusableCells enumerateObjectsUsingBlock:^(HMWaterflowViewCell *cell, BOOL *stop) { 201 if ([cell.identifier isEqualToString:identifier]) { 202 reusableCell = cell; 203 *stop = YES; 204 } 205 }]; 206 207 if (reusableCell) { // 從緩存池中移除 208 [self.reusableCells removeObject:reusableCell]; 209 } 210 return reusableCell; 211 } 212 213 #pragma mark - 私有方法 214 /** 215 * 判斷一個frame有無顯示在屏幕上 216 */ 217 - (BOOL)isInScreen:(CGRect)frame 218 { 219 return (CGRectGetMaxY(frame) > self.contentOffset.y) && 220 (CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height); 221 } 222 223 /** 224 * 間距 225 */ 226 - (CGFloat)marginForType:(HMWaterflowViewMarginType)type 227 { 228 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) { 229 return [self.delegate waterflowView:self marginForType:type]; 230 } else { 231 return HMWaterflowViewDefaultMargin; 232 } 233 } 234 /** 235 * 總列數 236 */ 237 - (NSUInteger)numberOfColumns 238 { 239 if ([self.dataSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) { 240 return [self.dataSource numberOfColumnsInWaterflowView:self]; 241 } else { 242 return HMWaterflowViewDefaultNumberOfColumns; 243 } 244 } 245 /** 246 * index位置對應的高度 247 */ 248 - (CGFloat)heightAtIndex:(NSUInteger)index 249 { 250 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) { 251 return [self.delegate waterflowView:self heightAtIndex:index]; 252 } else { 253 return HMWaterflowViewDefaultCellH; 254 } 255 } 256 257 #pragma mark - 事件處理 258 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 259 { 260 if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)]) return; 261 262 // 獲得觸摸點 263 UITouch *touch = [touches anyObject]; 264 // CGPoint point = [touch locationInView:touch.view]; 265 CGPoint point = [touch locationInView:self]; 266 267 __block NSNumber *selectIndex = nil; 268 [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, HMWaterflowViewCell *cell, BOOL *stop) { 269 if (CGRectContainsPoint(cell.frame, point)) { 270 selectIndex = key; 271 *stop = YES; 272 } 273 }]; 274 275 if (selectIndex) { 276 [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue]; 277 } 278 } 279 280 @end思路分析:1:瀑布流的核心代碼的封裝也是仿照tableView的底層實現:在tableView第一次加載的時候會首先調用一次刷新數據的方法,在下列方法中實現:
- (void)willMoveToSuperview:(UIView *)newSuperview{[self reloadData];},此方法willMoveToSuperview,是子view即將添加到superView時會調用一次。調用[self reloadData],模擬tableView第一次刷新表格 2:基本實現思路是,在reloadData方法中設置每一個cell的frame,存放到大數組中,再在layoutSubView里設置每一個view的frame。
2:在reloadData方法中,1:每執行一次reloadData方法,都要將原來的數據清空,來展示新的cell。其中self.displayingCells為正在顯示的cell的緩存字典,key值為每一個index,value為每一個index對應的正在顯示的cell(dic.allKeys,dic.allValues,分別獲得字典的所有的key值和value值得到一個數組,enum遍歷字典的時候,分別可得到字典的key值和value值)。2:在清空數據時,緩存字典,讓字典中每一個正在顯示的cell從父視圖上移除,并清空緩存字典,緩存池,還有裝有frame的數組,其中數組里有一個方法是遍歷每個元素讓每個元素都去執行某個方法[self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)]; 2:在reloadData中最主要的就是要求出每個cell的xy寬高,其中高度需要詢問代理,若實現了代理方法,則根據服務器返回圖片的寬高,按比例計算出相應的高度并返回。要想設置每個cell的frame,調用數據源方法,返回總的cell的個數,for循環遍歷每個cell,因為設置frame,要從最大y值最小的那列開始設置,所以在for循環遍歷每一個cell的時候,首先應找出最小y值的那一列,和最小y值。定義數組存放每一列的最大的y值,因為其為基本數據類型,所以用C語言數組存放,OC數組只能存放對象。先調用代理方法返回列數,若沒有實現代理方法,則默認返回的而是3列,定義C語言的數組,定義數組元素個數為numberOfColumns列數的數組,且數組中元素的基本類型都為CGFloat類型。并對數組中的元素初始化,遍歷數組,取出每個元素,賦值為0.0,float類型
??CGFloat maxYOfColumns[numberOfColumns];
? ? for (int i = 0; i<numberOfColumns; i++) {
? ? ? ? maxYOfColumns[i] = 0.0;
? ? }
3:找出最小y值所在的列號,并記錄此列下最大的y值。先假設最小的y值的列號是第0列,則最小y值列號下的最大的y值就為從C語言數組中根據列號取出的該列的最大y值,再for循環遍歷C語言數組,從列號為1開始遍歷,做判斷,從列號為1開始從數組中取出的最大y值還比定義的最大y值還小,則此時記錄下最小的y值的列號,和最小y值列號下的最大y值。直到找到最小y值的列號,此時,設置frame就從最小y值的列號處開始設置。
4:找到最小y值所在的列號后,開始設置cell的frame,寬度 = 屏幕寬度 - 左右間距 - (總列數 -1)*列間距,都可以求出,屏幕的高度,調用代理根據index返回該index處的cell的高度,(瀑布流圖片的寬高都為后臺服務器返回),若實現了代理高度的方法,則返回高度時,應該根據服務器返回圖片高度的比例計算出。若沒實現代理方法則返回默認的高度。?詢問代理i位置的高度?CGFloat cellH = [self heightAtIndex:i];X值的計算:計算x值和cell所在的列號有關,?CGFloat cellX = leftM + cellColumn * (cellW + columnM);Y值計算:和是否是首行有關,若是在首行,則y值為頂部間距,若不在首行,則y值為該列的最大y值加上一個行間距的值,是否是首行的判斷,則判斷C語言數組中每一列對應的最大y值,若為0,則在首行,若不為0,則不在首行,此時可以設置cell的frame
5:將設置好的cell的frame(結構體類型,非對象)封裝成OC對象,存放到數組中。?CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);?[self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
6:還要更新C語言數組中設置frame的所在列的最大y值,以便下次循環計算:maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame);
7:在設置完所有的frame后,要根據C語言數組中最大的y值,計算scrollView的contentSize,否則scrollView將不會滾動。如何找出最大的y值?先定義第0列為最大y值,將最大的y值從數組中取出,在for循環遍歷C語言數組,j從1開始,將數組中的每一列最大y值取出比較,若比這個最大的還大,則記錄下來,最后計算contentSize為最大y值加一個底部間距
?CGFloat contentH = maxYOfColumns[0];
? ? for (int j = 1; j<numberOfColumns; j++) {
? ? ? ? if (maxYOfColumns[j] > contentH) {
? ? ? ? ? ? contentH = maxYOfColumns[j];
? ? ? ? }
? ? }
? ? contentH += bottomM;
? ? self.contentSize = CGSizeMake(0, contentH);
?
3:layoutSubView中設置每一個cell的frame:1:遍歷存放cell所有frame的數組,取出每一個frame,CGRect cellFrame = [self.cellFrames[i] CGRectValue];首先先判斷是否在屏幕上,如果不在屏幕上,則將其cell從父視圖上移除,并且從緩存字典中移除,并加入到緩存池,NSMutableSet 所定義的集合中,同數組的用法是相同,存放對象,但數組是有序的,NSMutableSet是無序的。如果在屏幕上顯示,則證明已經計算好frame,則優先從緩存字典中根據索引取出cell。判斷取出的cell是否存在,不存在,則調用數據源方法,從緩存池中取或是自行創建,返回一個cell,設置frame,添加到scrollView上。并將新創建的cell存入緩存字典中。2:如何判斷是否在屏幕上,傳遞frame,根據frame判斷是否在屏幕上
- (BOOL)isInScreen:(CGRect)frame
{
? ? return (CGRectGetMaxY(frame) > self.contentOffset.y) &&
? ? (CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height);
}
4:根據重用標識從緩存池中取數據:遍歷集合,得到每一個cell,做條件過濾,判斷如果cell的重用標識符與傳入的標識符相等,則賦值cell,并停止遍歷,在block內部若是想賦值外部變量,則用__block修飾外部的變量,最后再判斷cell如果存在從緩存池中刪除,若是不刪除重用的cell,則其不到重用的作用,內存會被撐爆.
5:cell的點擊事件:1:若是想實現點擊事件:1:繼承UIControl的addTarget ?,touch或是valueChanged 2;添加手勢監聽器,要開啟用戶交互權限(如lable,iamgeView)3:實現touchBeagn,touchEnd,touchCancle方法,其中的touchBeagn剛開始點擊的時候調用,touchEnd結束點擊的時候調用。2:cell的點擊采取實現touch方法,實現的是touchEnd,點擊結束時的方法。并實現UITouch的方法。3:首先做條件過濾,若沒有實現該代理方法,直接返回,若實現了代理方法,先獲得觸摸點,?UITouch *touch = [touches anyObject];?CGPoint point = [touch locationInView:self];得到觸摸點,若是第二個參數為cell則觸摸點的位置是相對cell來計算的,若是為self,則是針對整個瀑布流控件的frame來計算的。其中touch.view獲得的是觸摸的view,也就是cell 。目的是求出index,調用代理將index傳過去,所以遍歷緩存字典,得到key,value值,key為index,value為cell,再判斷所得觸摸點在不在cell上,調用CGRectContainsPoint(cell.frame, point),如果在,則賦值index,停止遍歷,再外部判斷index值是否存在,存在則調用代理執行代理的點擊方法。@5或是@(3)兩個均為對象的表示形式,都為NSNumber類型。
7:自定義cell的封裝:
1 #import <UIKit/UIKit.h> 2 3 @interface HMWaterflowViewCell : UIView 4 @property (nonatomic, copy) NSString *identifier; 5 @end 1 #import "HMWaterflowViewCell.h" 2 3 @implementation HMWaterflowViewCell 4 5 - (id)initWithFrame:(CGRect)frame 6 { 7 self = [super initWithFrame:frame]; 8 if (self) { 9 // Initialization code 10 } 11 return self; 12 } 13 14 /* 15 // Only override drawRect: if you perform custom drawing. 16 // An empty implementation adversely affects performance during animation. 17 - (void)drawRect:(CGRect)rect 18 { 19 // Drawing code 20 } 21 */ 22 23 @end?
轉載于:https://www.cnblogs.com/cqb-learner/p/5733416.html
總結
以上是生活随笔為你收集整理的ios开发瀑布流框架的封装的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设计模式之职责链模式
- 下一篇: [Protobuf] Mac系统下安装配