最近需求中需要对首页进行改版,插入瀑布流效果,但是我的首页是collectionView创建的,里面布局太多了,不好修改,以前遇到这样的需求,当时直接想的不同的second设置不同的layout ,但是感觉太复杂了,对我来说有点难度,后面想到把上面的那部分全部设置为表头,只留一个瀑布流的layout实现,但是对于我们项目来说,这样写改动太大了,我还怕改出问题,所以就有了以下方式,先看下具体效果:
实现思路
- 瀑布流的那个效果单独做一个自定义的cell,里面设置瀑布流效果
- 难度就是计算高度的问题,这个cell的高度需要去计算,每次获取新数据的时候,都把所有数据遍历算一遍当前数据所瀑布流布局得到的高度
- 计算高度的时候要知道,因为我的是两列,所以 我把数组两个两个变成一个组合 组成大数组,然后遍历,计算左边高度 和 右边高度,相比较,哪边矮就先把数据布局到矮的那边,这样就可以得到最终的左右两边高度,当然记得加上行间距,然后再取一下左右高度最大值得到整个瀑布流的高度。
实现代码
//为什么要加号方法,因为我的cell高度计算是通过cell加号方法根据数据计算的,所以下面用的都是加号方法,
// 拆分数组变成2个2个一组
+ (NSArray *)splitArray:(NSArray *)originalArray withSubSize:(NSInteger)subSize
{
NSInteger count = originalArray.count % subSize == 0 ? (originalArray.count / subSize) : (originalArray.count / subSize + 1);
NSMutableArray *newArray = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < count; i ++) {
NSInteger j = i * subSize;
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
while (j < subSize*(i + 1) && j < originalArray.count) {
[tempArray addObject:[originalArray objectAtIndex:j]];
j += 1;
}
[newArray addObject:[tempArray copy]];
}
return [newArray copy];
}
+(CGFloat)caculateEndHeight:(OSFlowVideoModel *)model
{
CGFloat height = 0.0;
//左边的高度
CGFloat leftHeight = 0.0;
//右边的高度
CGFloat rightHeight = 0.0;
//最终高度
CGFloat contentHeight = 0.0;
//数组两个两个组成数据
NSArray *array = [self splitArray:model.list withSubSize:2];
for (int i = 0; i<array.count; i++) {
NSArray *arr = array[i];
for (int j = 0; j<2; j++) {
OSFlowVideoItemModel *a = arr[j];
if (leftHeight > rightHeight) {
rightHeight = rightHeight + [self caculateContentHeight:a] + PtOnIPhone6(7);
}else{
leftHeight = leftHeight + [self caculateContentHeight:a] + PtOnIPhone6(7);
}
}
}
contentHeight = MAX(leftHeight, rightHeight);
//多加了一个间距
height -= PtOnIPhone6(7);
height += contentHeight;
return height;
}
//计算每个item内容高度
+(CGFloat)caculateContentHeight:(OSFlowVideoItemModel *)item
{
CGFloat itemWidth = (OSScreenWidth - PtOnIPhone6(32) - PtOnIPhone6(7))/2;
CGFloat height = 0.0;
CGFloat titleH = [OSUtiltity getHeightWithText:item.title width:itemWidth - PtOnIPhone6(12) fontSize:RegularSysFont(14) withLimitHeight:PtOnIPhone6(40)];
CGFloat imageHeight = 0.0;
if (item.width.floatValue > item.height.floatValue) {
imageHeight = itemWidth*3/4;
}else if (item.width.floatValue < item.height.floatValue){
imageHeight = itemWidth*4/3;
}else{
imageHeight = itemWidth;
}
height = titleH + imageHeight + PtOnIPhone6(16) + PtOnIPhone6(11) + PtOnIPhone6(20) + PtOnIPhone6(16);
return height;
}
//计算的是cell高度,根据自身的情况来
+ (CGFloat)heightWithItems:(NSArray<OSStyleBaseItem *> *)items indexPath:(NSIndexPath *)indexPath width:(CGFloat)width
{
OSFlowVideoModel *model = (OSFlowVideoModel *)items[indexPath.row];
return [self caculateEndHeight:model];
}
瀑布流的collectionViewlayout
- h文件
@class EWWaterFallLayout;
@protocol EWWaterFallLayoutDataSource<NSObject>
@required
/**
* 每个item的高度
*/
- (CGFloat)waterFallLayout:(EWWaterFallLayout *)waterFallLayout heightForItemAtIndexPath:(NSUInteger)indexPath itemWidth:(CGFloat)itemWidth;
@optional
/**
* 有多少列
*/
- (NSUInteger)columnCountInWaterFallLayout:(EWWaterFallLayout *)waterFallLayout;
/**
* 每列之间的间距
*/
- (CGFloat)columnMarginInWaterFallLayout:(EWWaterFallLayout *)waterFallLayout;
/**
* 每行之间的间距
*/
- (CGFloat)rowMarginInWaterFallLayout:(EWWaterFallLayout *)waterFallLayout;
/**
* 每个item的内边距
*/
- (UIEdgeInsets)edgeInsetsInWaterFallLayout:(EWWaterFallLayout *)waterFallLayout;
@end
@interface EWWaterFallLayout : UICollectionViewLayout
/**
* 代理
*/
@property (nonatomic, weak) id<EWWaterFallLayoutDataSource> delegate;
@end
-m文件
/** 默认的列数 */
static const CGFloat EWDefaultColumnCount = 2;
/** 每一列之间的间距 */
static const CGFloat EWDefaultColumnMargin = 7;
/** 每一行之间的间距 **/
static const CGFloat EWDefaultFRowMargin = 7;
/** 内边距 */
static const UIEdgeInsets EWDefaultEdgeInsets = {0,0,0,0};
@interface EWWaterFallLayout()
/** 存放所有的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArr;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** 内容的高度 */
@property (nonatomic, assign) CGFloat contentHeight;
- (NSUInteger)columnCount;
- (CGFloat)columnMargin;
- (CGFloat)rowMargin;
- (UIEdgeInsets)edgeInsets;
@end
@implementation EWWaterFallLayout
- (NSMutableArray *)attrsArr {
if (!_attrsArr) {
_attrsArr = [NSMutableArray array];
}
return _attrsArr;
}
- (NSMutableArray *)columnHeights {
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
#pragma mark 数据处理
/**
* 列数
*/
- (NSUInteger)columnCount {
if ([self.delegate respondsToSelector:@selector(columnCountInWaterFallLayout:)]) {
return [self.delegate columnCountInWaterFallLayout:self];
} else {
return EWDefaultColumnCount;
}
}
/**
* 列间距
*/
- (CGFloat)columnMargin {
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterFallLayout:)]) {
return [self.delegate columnMarginInWaterFallLayout:self];
} else {
return EWDefaultColumnMargin;
}
}
/**
* 行间距
*/
- (CGFloat)rowMargin {
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterFallLayout:)]) {
return [self.delegate rowMarginInWaterFallLayout:self];
} else {
return EWDefaultFRowMargin;
}
}
/**
* item的内边距
*/
- (UIEdgeInsets)edgeInsets {
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterFallLayout:)]) {
return [self.delegate edgeInsetsInWaterFallLayout:self];
} else {
return EWDefaultEdgeInsets;
}
}
/**
* 初始化
*/
- (void)prepareLayout {
[super prepareLayout];
self.contentHeight = 0;
//清除之前计算的所有高度
[self.columnHeights removeAllObjects];
//设置每一列默认的高度
for (NSInteger i = 0; i < self.columnCount; i++) {
[self.columnHeights addObject:@(EWDefaultEdgeInsets.top)];
}
//清除之前所有的布局属性
[self.attrsArr removeAllObjects];
//开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
//创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//获取indexPath位置上cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArr addObject:attrs];
}
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
//创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
//设置布局属性的frame
CGFloat cellW = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
CGFloat cellH = [self.delegate waterFallLayout:self heightForItemAtIndexPath:indexPath.item itemWidth:cellW];
//找出最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (int i = 0; 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 maxColumnHeight = [self.columnHeights[destColumn] doubleValue];
if (self.contentHeight < maxColumnHeight) {
self.contentHeight = maxColumnHeight;
}
return attrs;
}
/**
* 决定cell的布局属性
*/
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return self.attrsArr;
}
/**
* 内容的高度
*/
- (CGSize)collectionViewContentSize {
return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}
@end
总结
难点主要在高度的计算那块,不要算错了,道理都一样,就和collectionViewlayout里面计算高度一样。
========2022.1.15更新==========
上面那种写法会存在这个问题:每次快速上拉获取新数据会导致内存增加,CPU会闪增,再降低,视图显示效果不好
优化
- 自定义UICollectionViewlayout 分区layout
具体实现,有时间写一个demo