瀑布流(Swift版和OC版)

原理分析 :

啥叫瀑布流?就是每次加载出来的cell(或者叫item)都是放到最短的那行,像瀑布流水一样出现,其关键思想就是如何计算出每个将要出现的cell的frame。即x、y、w、h。那咱就从浅入深、顺藤摸瓜的开始分析:
首先最好算的是cell的宽w,显然,根据列数、列与列之间的间距、以及距离屏幕边缘两侧即可算出。即:
w = 屏幕宽度 - 距屏幕两侧的间距 - (列数-1)* 列间距。
第二步:然后再看x。通过分析可以知道,其实只要知道了cell当前的列数,就可算出x。即:
x = 剧屏幕左侧的距离 + 当前列数 * (cell的宽(也就是w)加上列间距)
第三步:紧接着可以分析出,如果知道了某个cell的x,那肯定能知道此cell的y值,因为显然当前cell的y肯定就是当前列的高度(即最大y值),加上行间距即可。
这么分析下来,则重中之重,就是要找到最短的那一列,找到了,一切问题就迎刃而解了。
可以定义一个数组,用来存放列的高度值,再定义第0列是最短的那一列,遍历存放高度的数组,比较得出最小列的列数。然后得到了最小列的列数,则可以通过列的下标计算出x值,进而算出y值。即:
x = 剧屏幕左侧的距离 + 高度最小列的列数 * (cell的宽(也就是w)加上列间距)
y = 高度最小的高度值 + 行间距
算完y值之后,记得更新最短那列的高度值
高度h需要外界来确定,列数也是外界决定,更好运用疯转思想,高内聚、低耦合。

下面直接上轮子:

Swift版

协议

//Swift中定义协议: 必须遵守NSObjectProtocol
@objc protocol WaterfallsLayoutDelegate{
    ///返回每个item的宽高 必须实现
    func waterflowLayout(waterflowLayout:LZYWaterfallsLayout, itemIndex: Int, itemWidth:CGFloat) -> CGFloat

    ///返回列数 (非必须)
   optional func columnCountInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> Int
    
    ///返回列间距 (非必须)
    optional func columnMarginInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> CGFloat
    
    ///返回行间距 (非必须)
    optional func rowMarginInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> CGFloat
    
    ///返回边缘间距 上 左 下 右 (非必须)
    optional func edgeInsetsInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> UIEdgeInsets   
}

自定义的布局类

class LZYWaterfallsLayout: UICollectionViewLayout {
     ///定义一个属性保存代理对象 一定要加上weak, 避免循环引用
    weak var delegate: WaterfallsLayoutDelegate?
    /// 总列数
    var columnCount : Int {
        return delegate?.columnCountInWaterflowLayout?(self) ?? 2
    }
    /// 列间距
    var columnMargin: CGFloat {
        return delegate?.columnMarginInWaterflowLayout?(self) ?? 10
    }
    /// 行间距
    var rowMargin: CGFloat {
        return delegate?.rowMarginInWaterflowLayout?(self) ?? 10
    }
    /// 当前内边距
    var currentEdgeInsets: UIEdgeInsets {
        return delegate?.edgeInsetsInWaterflowLayout?(self) ?? UIEdgeInsets(top: 10,left: 10,bottom: 10,right: 10) //内边距
    }
    /// 布局属性数组
    private var attrsArray = [UICollectionViewLayoutAttributes]()
    /// 存放所有列的当前高度
    private var columnHeights = [CGFloat]()
    /// 内容的高度
    private var contentHeight:CGFloat = 0
    ///MARK: - 重写系统方法
    override func prepareLayout() {
        super.prepareLayout()
        // 清除以前计算的所有高度
        columnHeights.removeAll()
        
        for _ in 0.. [UICollectionViewLayoutAttributes]? {
        return attrsArray
    }
    ///返回indexPath位置cell对应的布局属性
    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attrs = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
        let collectionViewW = collectionView?.frame.size.width
        // 设置布局属性的frame
        let w = (collectionViewW! - currentEdgeInsets.left - currentEdgeInsets.right - CGFloat(columnCount - 1) * columnMargin) / CGFloat(columnCount)
        let h = delegate?.waterflowLayout(self, itemIndex: indexPath.item, itemWidth: w)
        // 找出高度最短的那一列
        var destColumn = 0
        var minColumnHeight = columnHeights[0]
        for i in 1.. columnHeight {
                minColumnHeight = columnHeight
                destColumn = i
            }
        }
        
        let x = currentEdgeInsets.left + CGFloat(destColumn) * (w + columnMargin)
        var y = minColumnHeight
        if y != currentEdgeInsets.top {
            y += rowMargin
        }
        attrs.frame = CGRectMake(x, y, w, h!)
        
        // 更新最短那列的高度
        columnHeights[destColumn] = CGRectGetMaxY(attrs.frame)
        
        // 记录内容的高度
        let columnHeight = columnHeights[destColumn]
        if contentHeight < columnHeight {
            contentHeight = columnHeight
        }
        return attrs
    }
    
    ///collectionView的ContentSize
    override func collectionViewContentSize() -> CGSize {
        return CGSizeMake(0, contentHeight + currentEdgeInsets.bottom)
    }
}

OC版

在.h文件中

//  FZXWaterfallsLayout.h
//
//  功用: 瀑布流的布局

#import 

@class FZXWaterfallsLayout;

// 2.构造方法,使得外界通过重写代理方面,控制细节
@protocol FZXWaterfallsLayoutDelegate 

@required
/**
 *  返回每个item的宽高 必须实现
 */
- (CGFloat)waterflowLayout:(FZXWaterfallsLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;

//可选实现
@optional
/**
 *  返回列数
 */
- (CGFloat)columnCountInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
/**
 *  返回列间距
 */
- (CGFloat)columnMarginInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
/**
 *  返回行间距
 */
- (CGFloat)rowMarginInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
/**
 *  返回边缘间距 上 左 下 右
 */
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(FZXWaterfallsLayout *)waterfallLayout;

@end

@interface FZXWaterfallsLayout : UICollectionViewLayout

/**
 *  设置代理,使得外界使用代理方面控制内部显示细节
 */
@property (nonatomic, weak) id delegate;

@end

在.m文件中

//  FZXWaterfallsLayout.m
//
//  Created by FZX on 16/5/20.
//
//  功用: 瀑布流的布局

#import "FZXWaterfallsLayout.h"

/** 默认的列数 */
static const NSInteger FZXDefaultColumnCount = 2;
/** 每一列之间的间距 */
static const CGFloat FZXDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat FZXDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets FZXDefaultEdgeInsets = {10, 10, 10, 10};

@interface FZXWaterfallsLayout ()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** 内容的高度 */
@property (nonatomic, assign) CGFloat contentHeight;

// 提供和重写get方法,实现对代理方法的实时监控
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;

@end

@implementation FZXWaterfallsLayout

#pragma mark - 初始化
- (void)prepareLayout
{
    [super prepareLayout];
    
    self.contentHeight = 0;
    
    // 清除以前计算的所有高度
    [self.columnHeights removeAllObjects];
    
    for (NSInteger i = 0; i < self.columnCount; i++)
    {
        [self.columnHeights addObject:@(self.edgeInsets.top)];
    }
    
    
    // 清除之前所有的布局属性
    [self.attrsArray removeAllObjects];
    // 开始创建每一个cell对应的布局属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++)
    {
        // 创建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 获取indexPath位置cell对应的布局属性
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

#pragma mark - 决定cell的排布
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

#pragma mark - 返回indexPath位置cell对应的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建布局属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    // collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;
    
    // 设置布局属性的frame
    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
    CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];
    
    // 找出高度最短的那一列
    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 x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
    CGFloat y = minColumnHeight;
    if (y != self.edgeInsets.top) {
        y += self.rowMargin;
    }
    attrs.frame = CGRectMake(x, y, w, h);
    
    // 更新最短那列的高度
    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
    
    // 记录内容的高度
    CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
    if (self.contentHeight < columnHeight) {
        self.contentHeight = columnHeight;
    }
    return attrs;
}

#pragma mark - collectionView的ContentSize
- (CGSize)collectionViewContentSize
{
    return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}

#pragma mark - 设置默认的一些属性
//列数
-(NSInteger)columnCount
{
    if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)])
    {
        return [self.delegate columnCountInWaterflowLayout:self];
    }
    else
    {
        return FZXDefaultColumnCount;
    }
}
//每一列的间距
-(CGFloat)columnMargin
{
    if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)])
    {
        return [self.delegate columnMarginInWaterflowLayout:self];
    }
    else
    {
        return FZXDefaultColumnMargin;
    }
}
//行间距
-(CGFloat)rowMargin
{
    if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)])
    {
        return [self.delegate rowMarginInWaterflowLayout:self];
    }
    else
    {
        return FZXDefaultRowMargin;
    }
}
//边缘
-(UIEdgeInsets)edgeInsets
{
    if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)])
    {
        return [self.delegate edgeInsetsInWaterflowLayout:self];
    }
    else
    {
        return FZXDefaultEdgeInsets;
    }
}

#pragma mark - 懒加载
-(NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

-(NSMutableArray *)columnHeights
{
    if (!_columnHeights) {
        _columnHeights = [NSMutableArray array];
    }
    return _columnHeights;
}

@end

你可能感兴趣的:(瀑布流(Swift版和OC版))