美文网首页iOS之功能细节iOS精选博文iOS精选博文
iOS实现较复杂的瀑布流布局效果

iOS实现较复杂的瀑布流布局效果

作者: 喵子G | 来源:发表于2017-02-14 13:52 被阅读6113次

实现效果

这里实现了一个较为复杂的瀑布流效果,在基本的高度不一的瀑布流上,增加了占据两列宽度大小的cell,效果如图:


fall.gif

具体实现

1,技术基础

这里使用了UICollectionView来展示瀑布流,通过自定义UICollectionView对象的collectionViewLayout,来实现对UICollectionView中cell的布局计算。
要自定义UICollectionView对象的collectionViewLayout,需要创建一个UICollectionViewLayout的子类。

@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;                                      ///< 最后一次大尺寸的列数
@property (nonatomic, assign) NSInteger lastFixIndex;                                         ///< 最后一次对齐矫正列数

- (CGFloat)columnCount;     ///< 列数
- (CGFloat)columnMargin;    ///< 列边距
- (CGFloat)rowMargin;       ///< 行边距
- (UIEdgeInsets)edgeInsets; ///< collectionView边距

熟悉在- (void)prepareLayout方法中遍历需要计算的cell,调用计算布局的方法,并将获取的布局属性保存到attrsArray数组中:

    // 当列高度数组为空时,即为第一行计算,每一列的基础高度加上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 indexPathForItem:i inSection:0];
        // 计算布局属性并将结果添加到布局属性数组中
        [self.attrsArray addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }

计算每个cell的布局需要用到columnHeights数组,这个数组的作用就是保存每一列的总高度,当一个cell要计算他的布局的时候,先看所有列哪一列的高度最小,就把cell放在哪一列下。这样cell的x和y就能够确定,cell的x为列数*cell的宽度+间距调节的值,cell的y值就是高度最小的那一列的总高度。

heights.png

cell的宽度和高度,我是通过随机数+不重复放大+选择性矫正的原则来计算的,下面有代码详细注释:

    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    // collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;
    // cell的宽度
    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right -
                 self.columnMargin * (self.columnCount - 1)) / self.columnCount;
    // cell的高度
    NSUInteger randomOfHeight = arc4random() % 100;
    CGFloat h = w * (randomOfHeight >= 50 ? 250 : 320) / 200;
    
    // 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;
    }
    
    // 随机数,用来随机生成大尺寸cell
    NSUInteger randomOfWhetherDouble = arc4random() % 100;
    
    // 判断是否放大
    if (destColumn < self.columnCount - 1                               // 放大的列数不能是最后一列(最后一列方法超出屏幕)
        && _noneDoubleTime >= 1                                         // 如果前个cell有放大就不放大,防止连续出现两个放大
        && (randomOfWhetherDouble >= 45 || _noneDoubleTime >= 8)        // 45%几率可能放大,如果累计8次没有放大,那么满足放大条件就放大
        && [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);
        // 当前cell列的高度就是当前cell的最大Y值
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        // 当前cell列下一列的高度也是当前cell的最大Y值,因为cell宽度*2,占两列
        self.columnHeights[destColumn + 1] = @(CGRectGetMaxY(attrs.frame));
    } else {
        // 正常cell的布局
        if (_noneDoubleTime <= 3 || _lastFixIndex == destColumn) {                     // 如果没有放大次数小于3且当前列等于上次矫正的列,就不矫正
            attrs.frame = CGRectMake(x, y, w, h);
        } else if (self.columnHeights.count > destColumn + 1                         // 越界判断
            && y + h - [self.columnHeights[destColumn + 1] doubleValue] < w * 0.1) { // 当前cell填充后和上一列的高度偏差不超过cell最大高度的10%,就和下一列对齐
            attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn + 1] doubleValue] - y);
            _lastFixIndex = destColumn;
        } else if (destColumn >= 1                                                   // 越界判断
                   && y + h - [self.columnHeights[destColumn - 1] doubleValue] < w * 0.1) { // 当前cell填充后和上上列的高度偏差不超过cell最大高度的10%,就和下一列对齐
            attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn - 1] doubleValue] - y);
            _lastFixIndex = destColumn;
        } else {
            attrs.frame = CGRectMake(x, y, w, h);
        }
        // 当前cell列的高度就是当前cell的最大Y值
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        _noneDoubleTime += 1;
    }
    // 返回计算获取的布局
    return attrs;
3,Demo源码地址:https://github.com/Joker-388/JKRComplexFallsDemo

可以在这里下载到完整的可运行Demo,项目中加了详细的注视。

如果觉得这篇文章对你有帮助,请关注一下,会不定期更新。

相关文章

  • 瀑布流布局 的学习

    1- 实现瀑布流布局效果 瀑布流效果瀑布流代码木桶布局效果木桶布局代码瀑布流布局 2- 实现一个新闻瀑布流新闻...

  • iOS实现较复杂的瀑布流布局效果

    实现效果 这里实现了一个较为复杂的瀑布流效果,在基本的高度不一的瀑布流上,增加了占据两列宽度大小的cell,效果如...

  • iOS瀑布流

    瀑布流 因为iOS提供UICollectionViewLayout的布局类不能实现瀑布流的效果,所以需要自定义一个...

  • 瀑布流布局_木桶布局

    题目1: 实现一个瀑布流布局效果 瀑布流 题目2:实现木桶布局效果 木桶布局 题目3:**实现一个新闻瀑布流新闻网...

  • 瀑布流布局

    题目1: 实现一个瀑布流布局效果瀑布流代码题目2:实现木桶布局效果木桶布局代码题目3:实现一个新闻瀑布流新闻网站,...

  • 瀑布流和懒加载结合

    实现一个瀑布流布局效果 瀑布流

  • 瀑布流布局_木桶布局

    1.实现一个瀑布流布局效果 JQ 瀑布流-1 效果 2.实现木桶布局效果 JQ 木桶布局 效果 3.实现一个新闻瀑...

  • 瀑布流布局

    瀑布流布局 实现一个瀑布流布局效果 预览 根据课程视频实现一个新闻瀑布流新闻网站,查看效果

  • 瀑布流布局&木桶布局

    一、实现一个瀑布流布局效果。 二、实现木桶布局效果。 预览 三、实现一个新闻瀑布流新闻网站。查看效果 jsonp ...

  • 瀑布流布局

    题目1:实现一个瀑布流布局效果 jsbin-实现瀑布流布局 题目2:根据课程视频实现一个瀑布流新闻网站,查看效果 ...

网友评论

  • petry:大神你好,JKRShopCell.xib不用xib可以实现这种瀑布流吗?
    petry:@喵子G 是的 我刚开始改写成纯代码遇到了下拉或者上拉 布局没有更新的问题 然后cell的layoutSubviews重新设置图片文字的布局就好了
    喵子G:这跟xib没关系呀,只不过手写的代码布局比较麻烦了,不太复杂的可以考虑用代码实现auto layout ,其实都是一样的
  • e46a376f96de:学到了:smile:
  • ea178e3dfb4b:大神,我照着你的demo写的,最后怎么不走cell里边的set方法啊,跑起来从plist里取不出来东西。
    喵子G:@最爱是深蓝 你再仔细检查一下代码吧,是不是哪里遗漏了
  • 爱勤海之旅:楼主,我现在需求是已知图片的宽高,根据图片的宽高按比例来显示,请问有什么思路吗?
    喵子G:@爱勤海之旅 可以啊,就是在计算布局的地方,把逻辑变一下就可以了,而且会很更简单
  • 明天就去:是不是没有加按照图片的实际宽高比来计算每个cell的宽高?一串图片放进去,明显变形了,如果再加上这个判断就完美了
    明天就去:刚才研究了下,不知道怎么才能把图片宽高传到Layout里面去:sweat:
    喵子G:@明天就去 确实没有这样做,因为没有找到这种图片资源,其实原理都是一样的,你可以在计算布局那里去根据宽高比来定义,而不是用我的随机数算法。
  • assassinate:看了 这么多, 兄弟 写的不错
  • 小菜99:第三张牛逼
  • 我的大名叫小爱:下载后pod install不成功怎么办啊 ...
    喵子G:@我的大名叫小爱 项目是很久之前创建的简单瀑布流该写的,现在版本下的Podfile格式有变化:
    platform :ios, "8.0"

    target ‘我的项目’ do

    pod 'AFNetworking', '~> 3.0'

    end
    我的大名叫小爱:@iOS开发技术分享 好的 我试试.
    喵子G:不需要pod install直接打开JKRFallsDemo.xcworkspace运行就行
  • 空转风:厉害了我的哥

本文标题:iOS实现较复杂的瀑布流布局效果

本文链接:https://www.haomeiwen.com/subject/xzxtwttx.html