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


      一直以来都想研究瀑布流的具体实现方法(起因是因为一则男女程序员应聘的笑话,做程序的朋友应该都知道)。最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的。但再用其布局之前必须了解其布局原理。为方便大家学习理解此处补上demo地址https://github.com/PurpleSweetPotatoes/CollcetionViewLayout_demo

      在这里笔者挑出其中较为重要的几个方法来进行讲解。

    1.- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 当collectionView视图位置有新改变(发生移动)时调用,其若返回YES则重新布局

    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属性根据滑动改变,此时就需要随时进行布局改变)

      瀑布流的实现示意图如下

    由图示意可看出除开最开始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的属性做法应该不会有太大弊端!如果笔者所做有什么错误或不妥之处望指出!谢谢!

  • 相关阅读:
    人月神话 画蛇添足
    人月神话 贵族专制和民主政治
    人月神话 外科手术队伍
    人月神话 焦油坑
    体温填报(五)
    体温填报(四)
    qwb与学姐
    qwb VS 去污棒
    1045 快速排序(25 分)
    LibreOJ #107. 维护全序集
  • 原文地址:https://www.cnblogs.com/purple-sweet-pottoes/p/4833558.html
Copyright © 2020-2023  润新知