iOS 瀑布流基本实现

作者: iOS_成才录 | 来源:发表于2015-11-13 20:41 被阅读17703次

一、瀑布流设计方案

不可取.png 过于复杂.png 最优方案.png

二、瀑布流设计思路分析

  • 1、自定义流水布局中,指定滚动方向、默认列数、行间距、列间距、以及指定cell的大小itemSize
  • 2、可以提供一个数组columnMaxYs(记录当前每一列的最大Y值),假如3列,我们就提供一个3个元素的数组,记录所有布局属性
      1. columnMaxYs实现懒加载,
    • 2.并在prepareLayout方法中 :
      • 1.先将columnMaxYs清空。
      • 2.再进行初始化每个元素为0.
      • 3.获取所有的Cell的布局属性,而每一个Cell的布局属性通过调用layoutAttributesForItemAtIndexPath:方式获取,而调用该方法,就会执行第4步

(至于为什么在prepareLayout方法中初始化,而不是在init方法初始化:
是因为,该方法每次刷新都会调用,而init方法中只会在创建布局对象的时候只执行一次,
例如:如果进行下拉刷新最新数据的时候,需求重新初始化数据,而如果我们使用的是init方法的话,并不会调用也就并不会清除之前的然后初始化,
而使用该方法prepareLayout可以办到,因为它会再次调用,而init不会调用)
```
+ 3.获取所有Cell的布局属性,
注意:对与所有Cell的布局属性,在第一次加载的时候需要计算一次,而当刷新collectionView的时候,当然也需要重新计算:所以我们提供一个布局属性的数组,来保存所有Cell的布局刷新,避免不必要的计算。
——> 放在哪? 计算最好呢?
———> 首选放在prepareLayout方法中,因为它会调用一次加载,而且当刷新的时候也会调用,满足需求,我们先将之前的全部移除,然后重新返回最新的布局属性数组; 但是,最好不要放在layoutAttributesForElementsInRect:方法(返回所有元素的布局属性数组中),因为改方法在滚动collectionView的时候,会频繁的调用,比较销毁性能。

  • 3、在layoutAttributesForElementsInRect:方法(返回所有元素的布局属性数组 )

    • 返回之前保存的所有Cell的布局属性数组
  • 4、我们可以在layoutAttributesForItemAtIndexPath: 方法: 来调整 Cell的布局属性 , 指定Cell的 frame

 1.在该方法中拿到当前cell的默认布局属性attrs,进行下面的调整,就可以实现瀑布流了
  2.遍历columnMaxYs数组,需要找出最短一列的 列号 与 最大Y值
  3. 确定当前Cell的 存放的x.y位置 以及widht与height
         —> width:根据屏幕宽度与列数以及每列的宽度求出.  height:服务器返回的
         —> x:需要根据当前最短一列的列号与Cell的宽度与间距可以求出来;y : 可以根据当前最短一列的最大Y值 + 行间距可以求出
 4.重新设置修改当前Cell的布局属性attrs 的 frame即可。—> 之前已经拿到当前Cell的 默认布局属性,以及上一步已经获取到当前Cell需要存放的x.y位置后,
  5.将修改Cell布局属性之后的,当前列当前Cell的Y值最大,所有我们要将该值记录到数组columnMaxYs中,以便下次对比
      ```

5、注意:我们需要设置collectionView的contentSize,它才会滚动,那么我们如何设置呢?

——> 在定义布局类中,系统提供了一个collectionViewContentSize的get对象方法(决定collectionView的contentSize)
—> contentSize的高度为:计算出最长那一列的最大Y值,也就是columnMaxYs的最大值 + 行间距
```

三、瀑布流的基本实现

效果图.png
  • 1.创建一个控制器JPCollectionViewController,继承UICollectionViewController,使用我们自定义的流水布局JPWaterflowLayout(实现见其后)
#import "JPCollectionViewController.h"
#import "JPWaterflowLayout.h" // 自定义流水布局

@interface JPCollectionViewController ()

@end

@implementation JPCollectionViewController

static NSString * const reuseIdentifier = @"cellID";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 切换布局
    self.collectionView.collectionViewLayout = [[JPWaterflowLayout alloc] init];
}

#pragma mark <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 30;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    cell.backgroundColor = [UIColor orangeColor];
    return cell;
}
@end
  • 2.自定义流水布局JPWaterflowLayout,继承UICollectionViewLayout
#import "JPWaterflowLayout.h"

#define JPCollectionW self.collectionView.frame.size.width

/** 每一行之间的间距 */
static const CGFloat JPDefaultRowMargin = 10;
/** 每一列之间的间距 */
static const CGFloat JPDefaultColumnMargin = 10;
/** 每一列之间的间距 top, left, bottom, right */
static const UIEdgeInsets JPDefaultInsets = {10, 10, 10, 10};
/** 默认的列数 */
static const int JPDefaultColumsCount = 3;

@interface JPWaterflowLayout()
/** 每一列的最大Y值 */
@property (nonatomic, strong) NSMutableArray *columnMaxYs;
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end

@implementation JPWaterflowLayout

#pragma mark - 懒加载
- (NSMutableArray *)columnMaxYs
{
    if (!_columnMaxYs) {
        _columnMaxYs = [[NSMutableArray alloc] init];
    }
    return _columnMaxYs;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [[NSMutableArray alloc] init];
    }
    return _attrsArray;
}

#pragma mark - 实现内部的方法
/**
 * 决定了collectionView的contentSize
 */
- (CGSize)collectionViewContentSize
{
    // 找出最长那一列的最大Y值
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        
        // 找出数组中的最大值
        if (destMaxY < columnMaxY) {
            destMaxY = columnMaxY;
        }
    }
    return CGSizeMake(0, destMaxY + JPDefaultInsets.bottom);
}

- (void)prepareLayout
{
    [super prepareLayout];
    
    // 重置每一列的最大Y值
    [self.columnMaxYs removeAllObjects];
    for (NSUInteger i = 0; i<JPDefaultColumsCount; i++) {
        [self.columnMaxYs addObject:@(JPDefaultInsets.top)];
    }
    
    // 计算所有cell的布局属性
    [self.attrsArray removeAllObjects];
    NSUInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSUInteger i = 0; i < count; ++i) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 说明所有元素(比如cell、补充控件、装饰控件)的布局属性
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 说明cell的布局属性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    /** 计算indexPath位置cell的布局属性 */
    
    // 水平方向上的总间距
    CGFloat xMargin = JPDefaultInsets.left + JPDefaultInsets.right + (JPDefaultColumsCount - 1) * JPDefaultColumnMargin;
    // cell的宽度
    CGFloat w = (JPCollectionW - xMargin) / JPDefaultColumsCount;
    // cell的高度,测试数据,随机数
    CGFloat h = 50 + arc4random_uniform(150);

    // 找出最短那一列的 列号 和 最大Y值
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    NSUInteger destColumn = 0;
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        
        // 找出数组中的最小值
        if (destMaxY > columnMaxY) {
            destMaxY = columnMaxY;
            destColumn = i;
        }
    }
    
    // cell的x值
    CGFloat x = JPDefaultInsets.left + destColumn * (w + JPDefaultColumnMargin);
    // cell的y值
    CGFloat y = destMaxY + JPDefaultRowMargin;
    // cell的frame
    attrs.frame = CGRectMake(x, y, w, h);
    
    // 更新数组中的最大Y值
    self.columnMaxYs[destColumn] = @(CGRectGetMaxY(attrs.frame));
    
    return attrs;
}
@end

相关文章

  • iOS 瀑布流基本实现

    一、瀑布流设计方案 二、瀑布流设计思路分析 1、自定义流水布局中,指定滚动方向、默认列数、行间距、列间距、以及指定...

  • iOS使用UICollectionView实现瀑布流

    UICollectionView实现瀑布流 在iOS中可以实现瀑布流的目前已知的有2种方案: 使用UIScroll...

  • iOS瀑布流

    瀑布流 因为iOS提供UICollectionViewLayout的布局类不能实现瀑布流的效果,所以需要自定义一个...

  • iOS瀑布流

    最新研究了下iOS中瀑布流的实现方法,一般瀑布流都采用ScrollView + UITableView,或者是使用...

  • 如何 布局一个collectionView瀑布流

    ios UICollectionView实现瀑布流 通过自定义collectionViewCell和重写colle...

  • 2018-06-21 项目2:实现UICollectionVie

    一.项目需求 二.实现列表 本次列表展示参考博客为ios - 用UICollectionView实现瀑布流详解具体...

  • UICollectionViewLayout之瀑布流

    原帖子:华山论剑之浅谈iOS瀑布流 我是模仿上面的代码自己写了一份,实现的是简单的瀑布流:就是把UICollect...

  • iOS UIScrollView实现瀑布流

    前言 一般来说,一个界面展示的图片的比例是不相同的,而为了让图片展示得比较好看——没有拉伸变形,也没有缩小后上下的...

  • iOS 瀑布流的实现

    三种方法实现 一、scrollView做垫子,上面添加多个tableView(不可取); 效率低下,cell不能循...

  • iOS-瀑布流实现

    思路:先说一下这个效果的实现思路,首先需要确定该瀑布流有多少列,然后需要确定每个cell 的高度,用一个数组记录下...

网友评论

  • 花果山松鼠:楼主,怎么让attrs能够只创建当前展示的cell,而不是所有的cell。这样往下拉,cpu占用越来越高。
  • 花果山松鼠:楼主的计算有些问题,需要优化。计算的高度会变化。
  • 禾子_____:看代码非常简单... 秒杀了我的问题:relaxed:
  • 肾得朕心:prepareLayout这个方法里,self.attrsArray为什么要先删除呢,而不是往后追加元素
    花果山松鼠:怎么去加新的元素。没法判断啊
  • 2e79dc331e8d:怎么加headview??
    且行且珍惜_iOS:https://www.jianshu.com/p/9fafd89c97ad 这里有答案😀
  • bd432dc7a216:能给我一个demo吗?875169289@qq.com 谢谢
  • 6a6156fbfbd5: :heart: :heart:
    感谢了,学懂了,一直想学一下自定义布局的
  • 4c5e3fcc055a:能给我一个demo吗 大神
    iOS_成才录:@5eb4a16edd8a 很多网上一搜,没写OC了,所以也没实例了,不好意思
    幸福的鱼:@咬肥皂的小鲨鱼 同求demo 404663549@qq.com
    4c5e3fcc055a:@咬肥皂的小鲨鱼 179434316@qq.com
  • 6a8c7e0e9557:这种方法配图一直不对 我也不知道为啥
  • YimG:按照方法做了 报错 UICollectionView must be initialized with a non-nil layout parameter
    34ff6d8eaf71:去 继承于UICollection 的类 init
    YimG:@iOS_成才录 不是 是跳界面没有设置 init Layout
    iOS_成才录:@YimG 你collectionView设置布局的问题, :blush:
  • 小宝宝爱吃棒棒糖:有没有demo啊
  • afd47268d3d0:我想问一下怎么能 把每个cell的高度传值给他
    afd47268d3d0:@pzx960927 因为照着你的这个方法写的话,进入页面会先走layout的获取高度的方法,再出去走获取数据的方法,这样的话就不能先把高度算好赋值到里面了。请问下你这个的解决方法是什么的呢
  • afd47268d3d0:我想请问下,照这样做之后无法给瀑布流加headerView了有什么解决方案吗
    afd47268d3d0:@zxc523341577 -(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
    0bed25dae9be:@pzx960927 我是新手,请问一下加一个sub具体是怎么做的呀?谢谢
    afd47268d3d0:@pzx960927 加上了在layout里面加一个sub..的方法就可以了
  • qiang2080:配图不错

本文标题:iOS 瀑布流基本实现

本文链接:https://www.haomeiwen.com/subject/cfqkhttx.html