collectionView

来源:互联网 发布:数据迁移整体解决方案 编辑:程序博客网 时间:2024/05/01 14:18
##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];
    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
    });
}
```



































0 0
原创粉丝点击