详解IOS中如何实现瀑布流效果

2020-01-15 17:53:11王冬梅

ios瀑布流原理,ios瀑布流demo,ios瀑布流代码

首先是初始化,并没有什么问题


- (instancetype)init {
  self = [super init];
  if (self) {
    _attributesArray = [NSMutableArray array];
    // 默认值设置为2列
    _numberOfColumns = 2;
    _contentHeight = 0.0f;
    _cellMargin = 5.0f;/**< 用来表示间距的属性 */
  }
  return self;
}

然后是getter方法,只需要使用点语法即可得到itemWidth的值(因为它就是固定的)


- (CGFloat)itemWidth {
  //所有边距的和.两列时有三个边距, 三列时有四个边距,逻辑强大就是好...
  CGFloat allMargin = (_numberOfColumns + 1) * _cellMargin;
  //除去边界之后的总宽度
  CGFloat noMarginWidth = CGRectGetWidth(self.collectionView.bounds) - allMargin;
  //出去边距的总宽度除以列数得到每一列的宽度(也就是itemWidth)
  return noMarginWidth / _numberOfColumns;
}

---接下来是难点---

必须重写的第一个方法


- (void)prepareLayout {
  // 定义变量记录高度最小的列,初始为第0列高度最小.
#pragma mark - 注意这个是从0开始算的啊!!!
  NSInteger shortestColumn = 0;
#pragma mark - 注意这个是从0开始算的啊!!!
  // 存储每一列的总高度.因为添加图片的列高度会变,所以需要定义一个数组来记录列的总高度.
  NSMutableArray *columnHeightArray = [NSMutableArray array];
  // 设置列的初始高度为边距的高度,没毛病!!!
  for (int i = 0; i < _numberOfColumns; i++) {
    // 所有列初始高度均设置为cell的间距
    [columnHeightArray addObject:@(_cellMargin)];
  }
  // 遍历collectionView中第 0 区中的所有item
  for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) {
    //需要用到这个玩意,提前拿到.
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    // 创建系统需要的布局属性对象,看后边的参数就知道这就是每个item的布局属性了
    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
    // 将布局属性放入数组中,这个数组当然是一开始定义的布局属性数组了
    [_attributesArray addObject:layoutAttributes];
    // 设置每个item的位置(x, y, width, height)
    // 横坐标的起始位置
#pragma mark - 比如一共两列,现在要放一张图片上去,需要放到高度最小的那一列.
#pragma mark - 假设第0列最短,那么item的x坐标就是从一个边距宽度那里开始.
#pragma mark - (itemWidth + cellMargin)为一个整体
    CGFloat x = (self.itemWidth + _cellMargin) * shortestColumn + _cellMargin;
    // 纵坐标就是 总高度数组 中最小列对应的高度
#pragma mark - 图片始终是添加在高度最小的那一列
    CGFloat y = [columnHeightArray[column] floatValue];/**<注意类型转换 */
    // 宽度没什么好说的
    CGFloat width = self.itemWidth;
#pragma mark - 这里给自定义的类声明了一个协议,通过协议得到图片的高度,调用时机就是需要item高度的时候
#pragma mark - 将Item的宽度传给代理人(ViewController),VC计算好高度后将高度返回给自定义类
#pragma mark - 也就是↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    CGFloat height = [self.delegate collectionView:self.collectionView
                        layout:self
                         width:self.itemWidth
               heightForItemAtIndexPath:indexPath];
    // 大功告成,设置item的位置信息,没什么好说
    layoutAttributes.frame = CGRectMake(x, y, width, height);
    // 上边废了半天劲放了一个item上去了,总高度数组是不是该更新一下数据了
    columnHeightArray[shortestColumn] = @([columnHeightArray[shortestColumn] floatValue] + height + _cellMargin);
    // 整个内容的高度,通过比较得到较大值作为整个内容的高度
    self.contentHeight = MAX(self.contentHeight, [columnHeightArray[shortestColumn] floatValue]);
    // 刚才放了一个item上去,那么此时此刻哪一列的高度比较低呢
    for (int i = 0; i < _numberOfColumns; i++) {
      //当前列的高度(刚才添加item的那一列)
      CGFloat currentHeight = [columnHeightArray[shortestColumn] floatValue];
      // 取出第i列中存放列高度
      CGFloat height = [columnHeightArray[i] floatValue];
      if (currentHeight > height) {
        //第i列高度(height)最低时,高度最低的列(shortestColumn)当然就是第i列了
        shortestColumn = i;
      }
    }
  }
// 思考下只使用上边的代码会出现什么问题
// 并不能影响心情,请不要恐慌...
// 提示:这个方法会被多次调用,数组
}