内容简介:利用UICollectionView实现一个随机放大的不规则瀑布流,图片宽高比和内容使用服务器接口返回的数据,效果如下图:接口:数据:
利用UICollectionView实现一个随机放大的不规则瀑布流,图片宽高比和内容使用服务器接口返回的数据,效果如下图:
接口:
https://www.easy-mock.com/mock/5cff89e36c54457798010709/shop/finderlist 复制代码
数据:
{ "data": [ { "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561019906083&di=2bbf7db2124067fe80739cce43a2b00e&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201410%2F05%2F20141005095943_QY5e8.jpeg", "width": "1200", "height": "2249" }, { "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561020045178&di=56eb95088ce1a23bbd16776ebcedb837&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Fc8107b13c3bfd1fa8835f5dc80c541b64c6b9e901a8f7-RLJBJP_fw658", "width": "658", "height": "872" }, ... { "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561020635692&di=cd0dedd961380917af46c536e7f6600b&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201707%2F29%2F20170729215622_tTLBP.thumb.700_0.jpeg", "width": "700", "height": "701" } ] } 复制代码
每个图片数据都指定了图片的宽和高,由于需要放大,而被放大占用两列宽度的图片顶部必须要对齐,所以需要将高度差别不大的两列高度做矫正,这里采用的规则是。
- 插入图片的高度距离左右某一张的高度差值小于的它高度20%就将图片高度强行对齐高度。
- 每一排不允许出现连续两个放大的图片。
- 每一列不允许出现连续连个放大的图片。
具体实现
1,重写的方法
创建一个UICollectionViewLayout的子类
@protocol JKRFallsLayoutDelegate <NSObject> @optional /// 列数 - (CGFloat)columnCountInFallsLayout:(JKRFallsLayout *)fallsLayout; /// 列间距 - (CGFloat)columnMarginInFallsLayout:(JKRFallsLayout *)fallsLayout; /// 行间距 - (CGFloat)rowMarginInFallsLayout:(JKRFallsLayout *)fallsLayout; /// collectionView边距 - (UIEdgeInsets)edgeInsetsInFallsLayout:(JKRFallsLayout *)fallsLayout; /// 返回图片模型 - (JKRImageModel *)modelWithIndexPath:(NSIndexPath *)indexPath; @end @interface JKRFallsLayout : UICollectionViewLayout @property (nonatomic, weak) id<JKRFallsLayoutDelegate> delegate; @end 复制代码
重写以下方法:
// collectionView 首次布局和之后重新布局的时候会调用 // 并不是每次滑动都调用,只有在数据源变化的时候才调用 - (void)prepareLayout { // 重写必须调用super方法 [super prepareLayout]; } // 返回布局属性,一个UICollectionViewLayoutAttributes对象数组 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { return [super layoutAttributesForElementsInRect:rect]; } // 计算布局属性 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { return [super layoutAttributesForItemAtIndexPath:indexPath]; } // 返回collectionView的ContentSize - (CGSize)collectionViewContentSize { return [super collectionViewContentSize]; } 复制代码
2,布局计算
要实现布局的计算,需要创建以下几个属性
@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *attrsArray; ///< 所有的cell的布局 @property (nonatomic, strong) NSMutableArray *columnHeights; ///< 每一列的高度 @property (nonatomic, assign) NSInteger noneDoubleTime; ///< 没有生成大尺寸次数 @property (nonatomic, assign) NSInteger lastDoubleIndex; ///< 最后一次大尺寸的列数 - (CGFloat)columnCount; ///< 列数 - (CGFloat)columnMargin; ///< 列边距 - (CGFloat)rowMargin; ///< 行边距 - (UIEdgeInsets)edgeInsets; ///< collectionView边距 复制代码
在- (void)prepareLayout方法中遍历需要计算的cell,调用计算布局的方法,并将获取的布局属性保存到attrsArray数组中:
- (void)prepareLayout { // 重写必须调用super方法 [super prepareLayout]; if ([self.collectionView numberOfItemsInSection:0] == PageCount && self.attrsArray.count > PageCount) { [self.attrsArray removeAllObjects]; [self.columnHeights removeAllObjects]; } // 当列高度数组为空时,即为第一行计算,每一列的基础高度加上collection的边框的top值 if (!self.columnHeights.count) { for (NSInteger i = 0; i < self.columnCount; i++) { [self.columnHeights addObject:@(self.edgeInsets.top)]; } } // 遍历所有的cell,计算所有cell的布局 for (NSInteger i = self.attrsArray.count; i < [self.collectionView numberOfItemsInSection:0]; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; // 计算布局属性并将结果添加到布局属性数组中 [self.attrsArray addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } } // 返回布局属性,一个UICollectionViewLayoutAttributes对象数组 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attrsArray; } // 返回collectionView的ContentSize - (CGSize)collectionViewContentSize { // collectionView的contentSize的高度等于所有列高度中最大的值 CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue]; for (NSInteger i = 1; i < self.columnCount; i++) { CGFloat columnHeight = [self.columnHeights[i] doubleValue]; if (maxColumnHeight < columnHeight) { maxColumnHeight = columnHeight; } } return CGSizeMake(0, maxColumnHeight + self.edgeInsets.bottom); } 复制代码
columnHeights保存保存每一列的总高度,cell的宽度和高度,通过随机数+不重复放大+矫正的原则来计算,下面有代码详细注释:
// 计算布局属性 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; // cell的宽度 CGFloat w = (self.collectionView.frame.size.width - self.edgeInsets.left - self.edgeInsets.right - self.columnMargin * (self.columnCount - 1)) / self.columnCount; // cell的高度 JKRImageModel*shop = [self.delegate modelWithIndexPath:indexPath]; CGFloat h = shop.height / shop.width * w; // cell应该拼接的列数 NSInteger destColumn = 0; // 高度最小的列数高度 CGFloat minColumnHeight = [self.columnHeights[0] doubleValue]; // 获取高度最小的列数 for (NSInteger i = 1; i < self.columnCount; i++) { CGFloat columnHeight = [self.columnHeights[i] doubleValue]; if (minColumnHeight > columnHeight) { minColumnHeight = columnHeight; destColumn = i; } } // 计算cell的x CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin); // 计算cell的y CGFloat y = minColumnHeight; if (y != self.edgeInsets.top) { y += self.rowMargin; } // 判断是否放大 if (destColumn < self.columnCount - 1 // 放大的列数不能是最后一列(最后一列方法超出屏幕) && _noneDoubleTime >= 1 // 如果前个cell有放大就不放大,防止连续出现两个放大 && arc4random() % 100 > 33 // 33%几率不放大 && [self.columnHeights[destColumn] doubleValue] == [self.columnHeights[destColumn + 1] doubleValue] // 当前列的顶部和下一列的顶部要对齐 && (_lastDoubleIndex != destColumn) // 最后一次放大的列不等当前列,防止出现连续两列出现放大不美观 ) { _noneDoubleTime = 0; _lastDoubleIndex = destColumn; // 重定义当前cell的布局:宽度*2,高度*2 attrs.frame = CGRectMake(x, y, w * 2 + self.columnMargin, h * 2 + self.rowMargin); self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); self.columnHeights[destColumn + 1] = @(CGRectGetMaxY(attrs.frame)); } else { // 正常cell的布局 if (self.columnHeights.count > destColumn + 1 && ABS(y + h - [self.columnHeights[destColumn + 1] doubleValue]) < h * 0.2) { // 当前cell填充后和上一列的高度偏差不超过cell最大高度的10%,就和下一列对齐 attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn + 1] doubleValue] - y); } else if (destColumn >= 1 && ABS(y + h - [self.columnHeights[destColumn - 1] doubleValue]) < h * 0.2) { // 当前cell填充后和上上列的高度偏差不超过cell最大高度的10%,就和下一列对齐 attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn - 1] doubleValue] - y); } else { attrs.frame = CGRectMake(x, y, w, h); } // 当前cell列的高度就是当前cell的最大Y值 self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); _noneDoubleTime += 1; } // 返回计算获取的布局 return attrs; } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。