collectionView佈局原理及瀑布流佈局方式

  一直以來都想研究瀑布流的具體實現方法(原由是由於一則男女程序員應聘的笑話,作程序的朋友應該都知道)。最近學習到了瀑布流的實現方法,瀑布流的實現方式有多種,這裏應用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的屬性作法應該不會有太大弊端!若是筆者所作有什麼錯誤或不妥之處望指出!謝謝!

相關文章
相關標籤/搜索