原理分析 :
啥叫瀑布流?就是每次加载出来的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..<columnCount {
columnHeights.append(currentEdgeInsets.top)
}
// 清除之前所有的布局属性
attrsArray.removeAll()
// 开始创建每一个cell对应的布局属性
let count = collectionView!.numberOfItemsInSection(0)
for i in 0..<count {
let indexPath = NSIndexPath(forItem: i, inSection: 0)
// 获取indexPath位置cell对应的布局属性
let attrs = layoutAttributesForItemAtIndexPath(indexPath)
attrsArray.append(attrs!)
}
}
///决定cell的排布
override func layoutAttributesForElementsInRect(rect: CGRect) -> [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..<columnCount {
// 取得第i列的高度
let columnHeight = columnHeights[i]
if minColumnHeight > 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 <UIKit/UIKit.h>
@class FZXWaterfallsLayout;
// 2.构造方法,使得外界通过重写代理方面,控制细节
@protocol FZXWaterfallsLayoutDelegate <NSObject>
@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<FZXWaterfallsLayoutDelegate> 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
网友评论