美文网首页UICollectionView
UICollectionView、瀑布流布局

UICollectionView、瀑布流布局

作者: 袁俊亮技术博客 | 来源:发表于2016-04-01 10:02 被阅读318次

    title : UICollectionView、瀑布流布局
    category : UI


    UICollectionView、瀑布流布局

    标签(空格分隔): UI


    [TOC]

    UICollectionViewController

    • UICollectionViewController必须在初始化的时候设置布局参数,通常使用系统提供的流水布局UICollectionViewFlowLayout
    // 创建流水布局
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    
    // 创建collectionView
    UICollectionViewController *collectionViewVc = [[UICollectionViewController alloc] initWithCollectionViewLayout:layout];
    
    // 设置collectionView的背景颜色
    collectionViewVc.collectionView.backgroundColor = [UIColor redColor];
    
    // 去掉滚动指示条
    collectionViewVc.collectionView.showsHorizontalScrollIndicator = NO;
    // 开启分页效果
    collectionViewVc.collectionView.pagingEnabled = YES;
    
    

    UICollectionView的数据源代理方法

    #pragma mark - UICollectionViewDataSource
    
    /**
     *  有几组
     */
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
    {
        return 10;
    }
    
    /**
     *  每一组有多少个cell
     */
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return 3;
    }
    
    /**
     *  每一个cell显示什么内容
     */
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *ID = @"cell";
        UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
        if (!cell) {
            cell = [[UICollectionViewCell alloc] init];
        }
        
        return cell;
    }
    

    UICollectionViewFlowLayout流水布局

    • layout可以用来设置cell的尺寸和位置
    • 基本属性参数
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    // 设置cell的尺寸
    layout.itemSize = CGSizeMake(100, 100);
    // 设置cell的间距
    layout.minimumInteritemSpacing = 20;
    // 设置每一行之间的间距
    layout.minimumLineSpacing = 20;
    // 设置每一组的内间距
    layout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10);
    // 设置滚动方向为水平方向
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    • UICollectionViewLayoutAttributes:布局属性
      • 1.一个cell对应一个UICollectionViewLayoutAttributes对象
      • 2.UICollectionViewLayoutAttributes对象决定了cell的frame
      • 注意每一个cell的原点是contentView的左上角,而不是CollectionView的左上角

    自定义布局常用几个方法

    • prepareLayout
      • 用来做布局的初始化操作(不建议在init方法中进行布局的初始化操作)
    - (void)prepareLayout
    
    • layoutAttributesForElementsInRect:
      • 这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)
      • 这个方法的返回值决定了rect范围内所有元素的排布(frame)
    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    
    • shouldInvalidateLayoutForBoundsChange:
      • 当collectionView的显示范围发生改变的时候,是否需要重新刷新布局
      • 一旦重新刷新布局,就会重新调用下面的方法:
        • 1.prepareLayout
        • 2.layoutAttributesForElementsInRect:方法
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    
    • targetContentOffsetForProposedContentOffset:
      • 这个方法的返回值,就决定了collectionView停止滚动时的偏移量
    - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
    

    实例程序

    实例一:横向图片浏览器

    注意该自定义布局是继承自UICollectionViewFlowLayout流水布局

    • 使用时只需要将UICollectionView的布局换成该布局即可
    • 小知识点:在给UIImageView加一个白色边框成相册的方法
      • 方法一:在给ImageView添加约束的时候分别在四周留出一定的空间,并设置cell的背景为白色即可
      • 方法二:给imageView加一个边框图层
    self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
    self.imageView.layer.borderWidth = 10;
    
    
    @implementation JLLineLayout
    
    /**
    *  用来做布局的初始化操作,不要在init方法中做布局的初始化操作
    */
    - (void)prepareLayout
    {
        [super prepareLayout];
        // 设置滚动方向为水平滚动
        self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        // 设置内边距
        CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
        self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
    }
    
    /**
    *  当collectionView的显示范围发生改变的时候是否需要重新刷新布局
    *   一旦重新刷新布局,就会重新调用下面的两个方法:
     1. prepareLayout
     2. layoutAttributesForElementsInRect
    *
    */
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    {
        return YES;
    }
    
    /**
    *  这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)
    *   这个方法的返回值决定了rect范围内所有元素的排布(frame)
    */
    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        // 获得super已经计算好的布局属性
        NSArray *array = [super layoutAttributesForElementsInRect:rect];
        
        // 计算collectionView最中心点的x值
        CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
    
        // 在super布局属性的基础上进行微调
        for (UICollectionViewLayoutAttributes *attrs in array) {
            // 计算cell的中心点X 和 collectionView最中心点的X值的间距
            CGFloat delta = ABS(attrs.center.x - centerX);
            // 根据间距,计算cell的缩放比例
            CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
            
            // 修改cell的缩放比例
            attrs.transform = CGAffineTransformMakeScale(scale, scale);
        }
        return array;
    }
    
    /**
     *  这个方法的返回值,决定了collectionView停止滚动时的偏移量
     */
    - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
    {
        // 计算出滚动停止时最终显示的矩形框
        CGRect rect;
        rect.origin.x = proposedContentOffset.x;
        rect.origin.y = 0;
        rect.size = self.collectionView.frame.size;
        // 获得super已经计算好的布局属性
        NSArray *array = [super layoutAttributesForElementsInRect:rect];
        
        // 计算collectionView最中心点的X值
        CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
        
        // 存放最小的间距
        CGFloat minDelta = MAXFLOAT;
        for (UICollectionViewLayoutAttributes *attrs in array) {
            if (ABS(minDelta) > ABS(centerX - attrs.center.x)) {
                minDelta = attrs.center.x - centerX;
            }
        }
        // 修改原有偏移量
        proposedContentOffset.x += minDelta;
        
        return proposedContentOffset;
    }
    
    @end
    
    

    实例二:瀑布流

    .h文件

    
    #import <UIKit/UIKit.h>
    @class JLWaterFlowLayout;
    
    @protocol JLWaterFlowLayoutDelegate <NSObject>
    @required
    /**
     *  根据传入的item的宽度返回每一个item的高度(必须实现)
     */
    - (CGFloat)waterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout heightForItemAtIndex:(NSInteger)index itemWidth:(CGFloat)itemWidth;
    
    @optional
    /** 总共显示多少列 */
    - (CGFloat)columnCountInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
    /** 每列之间的间距 */
    - (CGFloat)columnMarginInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
    /** 每一行之间的间距 */
    - (CGFloat)rowMarginInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
    /** collectionView的内边距 */
    - (UIEdgeInsets)edgeInsetsInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
    
    @end
    
    @interface JLWaterFlowLayout : UICollectionViewLayout
    /** 代理 */
    @property (nonatomic, weak) id<JLWaterFlowLayoutDelegate> delegate;
    
    @end
    

    .m文件

    
    #import "JLWaterFlowLayout.h"
    
    /** 默认的列数 */
    static const NSInteger JLDefaultColumnCount = 3;
    /** 默认的行间距 */
    static const CGFloat JLDefaultColumnMargin = 10;
    /** 默认的列间距 */
    static const CGFloat JLDefaultRowMargin = 10;
    /** 默认边缘间距 */
    static const UIEdgeInsets JLDefatultEdgeInsets = {10,10,10,10};
    
    @interface JLWaterFlowLayout()
    /** 存放所有的布局属性 */
    @property (nonatomic, strong) NSMutableArray *attrsArray;
    /** 存放所有列的当前高度 */
    @property (nonatomic, strong) NSMutableArray *columnHeights;
    /** 内容高度 */
    @property (nonatomic, assign) CGFloat contentHeight;
    
    /** 属性getter方法*/
    - (CGFloat)rowMargin;
    - (CGFloat)columnMargin;
    - (NSInteger)columnCount;
    - (UIEdgeInsets)edgeInsets;
    
    @end
    
    @implementation JLWaterFlowLayout
    
    - (NSMutableArray *)columnHeights
    {
        if (!_columnHeights) {
            _columnHeights = [NSMutableArray array];
        }
        return _columnHeights;
    }
    
    - (NSMutableArray *)attrsArray
    {
        if (!_attrsArray) {
            _attrsArray = [NSMutableArray array];
        }
        return _attrsArray;
    }
    
    /**
     *  初始化
     */
    - (void)prepareLayout
    {
        // 1.注意要调用super的prepareLayout
        [super prepareLayout];
        
        // 清除以前计算的所有高度
        [self.columnHeights removeAllObjects];
        // 为所有列设置一个默认的高度
        for (NSInteger i = 0; i < self.columnCount; i++) {
            [self.columnHeights addObject:@(JLDefatultEdgeInsets.top)];
        }
        
        // 清除之前所有的布局属性,如果不清除的话,self.attrsArray将会越来越大
        [self.attrsArray removeAllObjects];
        
        // 2.开始创建每一个cell对应的布局属性
        // 2.1查看对应的第0组有多少个item
        NSInteger count = [self.collectionView numberOfItemsInSection:0];
        
        for (NSInteger i = 0; i < count; i++) {
            // 取出对应第0组第i个item对应的indexPath
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
            
            // 获取indexPath位置cell对应的布局属性
            UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
            
            // 添加布局属性
            [self.attrsArray addObject:attrs];
        }
    }
    
    /**
     *  决定rect范围内cell的排布
     *  这个方法在继承自UICollectionViewLayout的情况下,只要拖动UICollectionView就会调用。
     *  但是如果在UICollectionViewFlowLayout情况下则不会调用很频繁,因为UICollectionViewFlowLayout布局会对它进行控制
     *  由于计算item的布局只需要计算一次,所以,解决方案是将计算item布局的操作放到prepareLayout保存到一个数组中。在这里直接返回即可
     */
    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        return self.attrsArray;
    }
    
    /**
     *  返回indexPath位置cell对应的布局属性
     */
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        // 为第indexPath的item创建一个布局属性
        UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        
        // 设置布局属性
        // collectionView的宽度
        CGFloat collectionViewW = self.collectionView.frame.size.width;
        
        CGFloat cellW = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
        CGFloat cellH = [self.delegate waterFlowLayout:self heightForItemAtIndex:indexPath.item itemWidth:cellW];
        
        // 找出高度最短的那一列
        // 当前找到的最短的列号
        NSInteger destColumn = 0;
        // 当前高度最小的那列的高度
        CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
        for (NSInteger i = 1; i < self.columnCount; i++) {
            // 取得第i列的高度
            CGFloat columnHeight = [self.columnHeights[i] doubleValue];
            
            if (minColumnHeight > columnHeight) {
                minColumnHeight = columnHeight;
                destColumn = i;
            }
        }
        
        CGFloat cellX = self.edgeInsets.left + destColumn * (cellW + self.columnMargin);
        CGFloat cellY = minColumnHeight;
        
        if (cellY != self.edgeInsets.top) {
            cellY += self.rowMargin;
        }
        attrs.frame = CGRectMake(cellX, cellY, cellW, cellH);
        // 更新最短那列的高度
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        
        // 记录内容的高度
        CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
        if (self.contentHeight < columnHeight) {
            self.contentHeight = columnHeight;
        }
        
        // 返回布局属性
        return attrs;
    }
    
    /**
     *  contentSize,设置collectionView的滚动范围
     */
    - (CGSize)collectionViewContentSize
    {
        return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom + 100);
    }
    
    #pragma mark - <JLWaterFlowLayoutDelegate>
    
    - (CGFloat)rowMargin
    {
        if ([self.delegate respondsToSelector:@selector(rowMarginInWaterFlowLayout:)]) {
            return [self.delegate rowMarginInWaterFlowLayout:self];
        }else{
            return JLDefaultRowMargin;
        }
    }
    
    - (CGFloat)columnMargin
    {
        if ([self.delegate respondsToSelector:@selector(columnMarginInWaterFlowLayout:)]) {
            return [self.delegate columnMarginInWaterFlowLayout:self];
        }else{
            return JLDefaultColumnMargin;
        }
    }
    
    - (NSInteger)columnCount
    {
        if ([self.delegate respondsToSelector:@selector(columnCountInWaterFlowLayout:)]) {
            return [self.delegate columnCountInWaterFlowLayout:self];
        }else{
            return JLDefaultColumnCount;
        }
    }
    
    - (UIEdgeInsets)edgeInsets
    {
        if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterFlowLayout:)]) {
            return [self.delegate edgeInsetsInWaterFlowLayout:self];
        }else{
            return JLDefatultEdgeInsets;
        }
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:UICollectionView、瀑布流布局

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