一直以來都想研究瀑布流的具體實現方法(原由是由於一則男女程序員應聘的笑話,作程序的朋友應該都知道)。最近學習到了瀑布流的實現方法,瀑布流的實現方式有多種,這裏應用collectionView來重寫其UICollectionViewLayout進行佈局是最爲簡單方便的。但再用其佈局以前必須瞭解其佈局原理。爲方便你們學習理解此處補上demo地址https://github.com/PurpleSweetPotatoes/CollcetionViewLayout_demogit
在這裏筆者挑出其中較爲重要的幾個方法來進行講解。程序員
1.- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 當collectionView視圖位置有新改變(發生移動)時調用,其若返回YES則從新佈局github
2.- (void)prepareLayout 準備好佈局時調用。此時collectionView全部屬性都已肯定。讀者在這裏能夠將collectionView當作畫布,有了畫布後,咱們即可以在其上面畫出每一個item數組
3.- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 返回collectionView視圖中全部視圖的屬性(UICollectionViewLayoutAttributes)數組佈局
4.- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 返回indexPath對應item的屬性學習
5.- (CGSize)collectionViewContentSize 設置collectionView的可顯示範圍測試
這些方法中最重要的即是3,4方法,在3方法中返回全部視圖屬性數組,並根據這些屬性進行佈局,而4方法則返回每一個item的屬性,咱們則在這裏設置每一個item的屬性(主要是frame),就可讓collectionView按照咱們的意願進行佈局了!(在這裏咱們不須要用到1方法,若item屬性根據滑動改變,此時就須要隨時進行佈局改變)atom
瀑布流的實現示意圖以下spa
由圖示意可看出除開最開始3個item,後面的item都是存放3列中的最短列上面,所以咱們只須要計算出每一個item的frame,並擺放的話那咱們的瀑布流天然就成功了。設計
.h文件中
1 typedef CGFloat(^HeightBlock)(NSIndexPath *indexPath , CGFloat width); 2 @interface BQWaterLayout : UICollectionViewLayout 3 /** 列數 */ 4 @property (nonatomic, assign) NSInteger lineNumber; 5 /** 行間距 */ 6 @property (nonatomic, assign) CGFloat rowSpacing; 7 /** 列間距 */ 8 @property (nonatomic, assign) CGFloat lineSpacing; 9 /** 內邊距 */ 10 @property (nonatomic, assign) UIEdgeInsets sectionInset; 11 /** 12 * 計算各個item高度方法 必須實現 13 * 14 * @param block 設計計算item高度的block 15 */ 16 - (void)computeIndexCellHeightWithWidthBlock:(CGFloat(^)(NSIndexPath *indexPath , CGFloat width))block; 17 @end
爲了方便修改瀑布流的佈局咱們須要設置排列布局的各個接口,由於瀑布流中只能經過列數計算出item的寬,所以須要使用computeIndexCellHeightWithWidthBlock來計算出每一個item的高度(利用寬高比)!
.m文件中
代碼中註釋已經寫的很明白了,無需多作解釋,此處寫法只能實現item的佈局,不能添加headview或footview!
1 @interface BQWaterLayout() 2 /** 存放每列高度字典*/ 3 @property (nonatomic, strong) NSMutableDictionary *dicOfheight; 4 /** 存放全部item的attrubutes屬性*/ 5 @property (nonatomic, strong) NSMutableArray *array; 6 /** 計算每一個item高度的block,必須實現*/ 7 @property (nonatomic, copy) HeightBlock block; 8 @end 9 10 @implementation BQWaterLayout 11 - (instancetype)init 12 { 13 self = [super init]; 14 if (self) { 15 //對默認屬性進行設置 16 /** 17 默認行數 3行 18 默認行間距 10.0f 19 默認列間距 10.0f 20 默認內邊距 top:10 left:10 bottom:10 right:10 21 */ 22 self.lineNumber = 3; 23 self.rowSpacing = 10.0f; 24 self.lineSpacing = 10.0f; 25 self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 26 _dicOfheight = [NSMutableDictionary dictionary]; 27 _array = [NSMutableArray array]; 28 } 29 return self; 30 } 31 32 /** 33 * 準備好佈局時調用 34 */ 35 - (void)prepareLayout{ 36 [super prepareLayout]; 37 NSInteger count = [self.collectionView numberOfItemsInSection:0]; 38 //初始化好每列的高度 39 for (NSInteger i = 0; i < self.lineNumber ; i++) { 40 [_dicOfheight setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",i]]; 41 } 42 //獲得每一個item的屬性值進行存儲 43 for (NSInteger i = 0 ; i < count; i ++) { 44 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; 45 [_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 46 } 47 } 48 /** 49 * 設置可滾動區域範圍 50 */ 51 - (CGSize)collectionViewContentSize{ 52 NSLog(@"collectionViewContentSize"); 53 __block NSString *maxHeightline = @"0"; 54 [_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 55 if ([_dicOfheight[maxHeightline] floatValue] < [obj floatValue] ) { 56 maxHeightline = key; 57 } 58 }]; 59 return CGSizeMake(self.collectionView.bounds.size.width, [_dicOfheight[maxHeightline] floatValue] + self.sectionInset.bottom); 60 } 61 /** 62 * 計算indexPath下item的屬性的方法 63 * 64 * @return item的屬性 65 */ 66 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ 67 //經過indexPath建立一個item屬性attr 68 UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 69 //計算item寬 70 CGFloat itemW = (self.collectionView.bounds.size.width - (self.sectionInset.left + self.sectionInset.right) - (self.lineNumber - 1) * self.lineSpacing) / self.lineNumber; 71 CGFloat itemH; 72 //計算item高 73 if (self.block != nil) { 74 itemH = self.block(indexPath, itemW); 75 }else{ 76 NSAssert(itemH != 0,@"Please implement computeIndexCellHeightWithWidthBlock Method"); 77 } 78 //計算item的frame 79 CGRect frame; 80 frame.size = CGSizeMake(itemW, itemH); 81 //循環遍歷找出高度最短行 82 __block NSString *lineMinHeight = @"0"; 83 [_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 84 if ([_dicOfheight[lineMinHeight] floatValue] > [obj floatValue]) { 85 lineMinHeight = key; 86 } 87 }]; 88 int line = [lineMinHeight intValue]; 89 //找出最短行後,計算item位置 90 frame.origin = CGPointMake(self.sectionInset.left + line * (itemW + self.lineSpacing), [_dicOfheight[lineMinHeight] floatValue]); 91 _dicOfheight[lineMinHeight] = @(frame.size.height + self.rowSpacing + [_dicOfheight[lineMinHeight] floatValue]); 92 attr.frame = frame; 93 94 return attr; 95 } 96 /** 97 * 返回視圖框內item的屬性,能夠直接返回全部item屬性 98 */ 99 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ 100 return _array; 101 } 102 /** 103 * 設置計算高度block方法 104 * 105 * @param block 計算item高度的block 106 */ 107 - (void)computeIndexCellHeightWithWidthBlock:(CGFloat (^)(NSIndexPath *, CGFloat))block{ 108 if (self.block != block) { 109 self.block = block; 110 } 111 } 112 @end
至此一個簡單的collectionViewLayout瀑布流佈局便設置完成,只須要在本身代碼中使用此佈局即可以獲得一個瀑布流了!
下圖是筆者的效果圖:
2列效果 3列效果
後記:
筆者原本開始還擔憂若是item過多,那麼設置的屬性就會過多,好比數組內存放一千或一萬個item的屬性,後來通過筆者測試後發現,系統應該每次都是事先計算好了全部item的屬性(經過tableView計算每行高度的代理方法來思考),所以直接初始化好全部item的屬性作法應該不會有太大弊端!若是筆者所作有什麼錯誤或不妥之處望指出!謝謝!