UICollectionView

UICollectionView、瀑布流布局

2016-04-01  本文已影响318人  袁俊亮技术博客

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


UICollectionView、瀑布流布局

标签(空格分隔): UI


[TOC]

UICollectionViewController

// 创建流水布局
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流水布局

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;

自定义布局常用几个方法

- (void)prepareLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity

实例程序

实例一:横向图片浏览器

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

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
上一篇下一篇

猜你喜欢

热点阅读