您的位置:首页 > 其它

collectionView

2016-06-16 23:23 183 查看
##collectionView图片无限滚动实现的思想

* collectionView只有3个Cell。每次滚动结束后,都让collectionView以非动画方式,滚回到第一个Cell(Cell从序数0开始)上,以保证每次拖拽滚动结束后,collectionView依然可以滚动

* 通过监听collectionView的滚动方向对应修改图片的全局索引值,每次collectionView自动滚回到第一个Cell,在collectionView创建Cell时,将对应的内容设置给collectionView的第一个Cell

* collectionView刷新Cell前关闭动画,刷新后重新开启动画

```objc

//  监听collectionView的滚动事件

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

    

    //  根据滚动结束后,collectionView的内容偏移值,计算出要显示的图片索引的变化

    CGFloat offset = self.collectionView.contentOffset.x;

    NSInteger offsetPage = (offset / self.view.frame.size.width) - 1;

    

    //  未翻页,什么都不做

    if (!offsetPage) {

        return;

    }

    

    //  +总页数对总页数取模,保证不越界

    self.imageIndex = (self.imageIndex + offsetPage + self.images.count) % self.images.count;

    

    //  每次滚动结束后,重新让collectionView滚动到中间Cell上

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:1 inSection:0];

    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];

    

    //  必须要刷新Cell。

    //  每次新Cell出现时,都调用了代理返回Cell的方法,刷新了数据。为什么会出现Cell显示图片错误的问题?

    //  这个显示bug是由于collectionView多线程任务执行完成顺序先后造成的

    [UIView setAnimationsEnabled:NO];

    [self.collectionView reloadItemsAtIndexPaths:@[indexPath]];

    [UIView setAnimationsEnabled:YES];

}

```

###-viewDidAppear: 

程序启动viewDidLoad方法执行完时,collectionView的代理方法还没有调用。此时设置collectionView的Cell的相关属性和数据,是无意义的。必须要等到View被显示之后(所有的相关方法都调用执行完毕),才可以设置collectionView的Cell的相关参数和属性

---

##瀑布流布局实现思路及具体步骤

###思路

1. 定宽不定高:列宽相等,每一个item的高度由内容决定。

2. 每列下面一个Cell的Y轴起点,由上一个Cell的底部决定

3. collectionView的内容尺寸,由Cell最大的Y值决定

###实现步骤

1. 为collectionView注册xib模板的Cell,设置Cell的子控件及约束

2. 实现collectionView的数据源协议方法,布局Cell

3. 自定义继承自UICollectionViewFlowLayout类的子类,重写父类的某些方法,手动计算每一个Cell对应的属性

4. 设置collectionView的flowLayout参数关联新建的自定义类

5. storyboard中给collectionView添加footerView,并关联自定义类,实现数据源方法中返回页脚View方法

6. 实现新增数据并刷新的方法,当FooterView 被创建后,自动执行并刷新数据

$$控制器类相关属性$$

```objc

@property (nonatomic, strong) NSMutableArray *goods;

@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;

@property (weak, nonatomic) IBOutlet ASWaterFlowLayout *flowLayout;

@property (nonatomic, weak) ASFooterView *footerView;

```

###自定义flowLayout类计算每个Cell的frame属性

$$属性$$

```objc

@property (nonatomic, assign) NSInteger columnCount;    //  排布列数

@property (nonatomic, weak) id<ASWaterFlowLayoutDelegate> delegate; // 获取Cell高度的代理对象

```

$$代理协议$$

```objc

@protocol ASWaterFlowLayoutDelegate <NSObject>

//  根据Cell序数及宽度,获取Cell高度

- (CGFloat)waterFlowLayoutGetCellHeight:(ASWaterFlowLayout *)waterFlowLayout

                withIndex:(NSInteger)index andCellWidth:(CGFloat)cellWidth;

@end

```

$$延展属性$$

```objc

@property (nonatomic, strong) NSMutableArray *arrayAttrs;    // 自定义保存每个Cell属性的数组

@property (nonatomic, strong) NSMutableDictionary *dictMaxY;  // 自定义保存每行当前Cell总高度的字典

@property (nonatomic, copy) NSString *keyMaxY;  // 字典中最大Y值对应的key

@property (nonatomic, copy) NSString *keyMinY;  // 字典中最小Y值对应的key

```

$$字典和数组的懒加载$$

```objc

- (NSMutableArray *)arrayAttrs{

    if (_arrayAttrs == nil)  _arrayAttrs = [NSMutableArray array];

    return _arrayAttrs;

}

- (NSMutableDictionary *)dictMaxY{

    if (!_dictMaxY) _dictMaxY = [NSMutableDictionary dictionary];

    return _dictMaxY;

}

```

$$重写prepareLayout方法$$

```objc

- (void)prepareLayout{

    [super prepareLayout];

    

    //  初始化字典,让字典拥有三组键值

    for (int i=0; i<self.columnCount; i++) {

        NSString *key = [NSString stringWithFormat:@"%d", i];

        self.dictMaxY[key] = @"0";

    }

    

    //  每次刷新数据准备布局collectionView时,清除所有Cell的layout属性

    [self.arrayAttrs removeAllObjects];

    

    //  计算所有Cell的属性

    //  获取collectionView第0组的总Cell数

    NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];

    for (int i=0; i<cellCount; i++) {

        

        //  获取当前Cell所在的坐标

        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];

        //  获取对应Cell的属性数据

        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath];

        

        //  将当前Cell的属性信息添加到Cell属性数组中

        [self.arrayAttrs addObject:attr];

    }

    

    //  计算footerView的属性

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

    UICollectionViewLayoutAttributes *attrFooter = [UICollectionViewLayoutAttributes

            layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:indexPath];

    

    //  根据内容最大Y值,设置footerView的属性

    NSString *maxHeight = self.keyMaxY;

    attrFooter.frame = CGRectMake(0, [self.dictMaxY[maxHeight] floatValue],

            self.collectionView.frame.size.width, self.footerReferenceSize.height);

    

    [self.arrayAttrs addObject:attrFooter];

}

```

**注:**属性数组中,可以重复为指定indexPath的Cell添加多个不同的属性实例,最后布局时,以最后一个被添加进数组的属性实例为准。但是,不可以添加多于1个footerView的属性实例!新添加进的footerView的属性实例不会在布局时替换旧值,而是直接冲突报错!

![](Snip20160117_2.png)

$$计算每个item的frame并返回$$

```objc

//  计算Cell的属性。控制器每布局一个Cell,都会来调用一次这个方法

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{

    

    //  计算每一个Cell的frame

    CGFloat contentW = self.collectionView.frame.size.width - self.sectionInset.left -

            self.sectionInset.right - self.minimumInteritemSpacing * (self.columnCount - 1);

    //  计算每个Cell的宽,代理获取对应的高

    CGFloat cellW = contentW / self.columnCount;

    CGFloat cellH = [self.delegate waterFlowLayoutGetCellHeight:self withIndex:indexPath.item andCellWidth:cellW];

    

   //  将Cell布局在Y值最小的列

    NSInteger col = [self.keyMinY integerValue];

 
4000
  CGFloat cellX = self.sectionInset.left + col * (cellW + self.minimumInteritemSpacing);

    

    NSString *keyCol = [NSString stringWithFormat:@"%ld", col];

    CGFloat cellY = [self.dictMaxY[keyCol] floatValue];

    

    //  更新字典中布局Cell所在列的最大高度

    CGFloat maxY = cellY + cellH + self.minimumInteritemSpacing;

    self.dictMaxY[keyCol] = [NSString stringWithFormat:@"%f", maxY];

    

    //  修改对应Cell在数组中保存的属性值

    UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attr.frame = CGRectMake(cellX, cellY, cellW, cellH);

    

    //  返回Cell的属性值

    return attr;

}

```

$$返回可视范围内所有Cell的布局属性泛型数组$$

```objc

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{

    return self.arrayAttrs;

}

```

$$返回collectionView的contentSize$$

```objc

- (CGSize)collectionViewContentSize{

    return CGSizeMake(0, [self.dictMaxY[self.keyMaxY] floatValue] + self.footerReferenceSize.height);

}

```

$$获取字典中最大/小Y值对应的key$$

```objc

- (NSString *)keyMaxY{

    //  假定最大的key为第0组键值的key

    __block NSString *keyMaxY = @"0";

    //  block方式遍历字典,比较Y值,获取最大的key

    [self.dictMaxY enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull value, BOOL * _Nonnull stop) {

        if ([self.dictMaxY[keyMaxY] floatValue] < [value floatValue]) {

            keyMaxY = key;

        }

    }];

    return keyMaxY;

}

```

###刷新数据功能模块

$$更新数据数组$$

类似于懒加载,根据新数据plist文件,构建模型数组,数组中的模型元素添加到原数据数组中

$$滚出FooterView之后的刷新数据事件$$

```objc

//  监听collectionView的滚动位置。当footerView被显示出来后,执行这个方法中的内容

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    //  FooterView没有出现或者指示器还在旋转时,返回

    if (!self.footerView || [self.footerView.activityIndicator isAnimating])   return;

    

    [self.footerView.activityIndicator startAnimating];    //  指示器开始动画

    

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        [self reloadData];  //  更新数据数组

        [self.collectionView reloadData];   // collectionView刷新数据

        [self.footerView.activityIndicator stopAnimating]; // 指示器停止动画

        self.footerView = nil; // 释放FooterView

    });

}

```
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: