iOS瀑布流详细介绍

  • 传统!!!依然是效果演示
瀑布流效果.gif
  • 特点:可以自由设置瀑布流的总列数(效果演示为2列)

虽然iphone手机的系统相册没有使用这种布局效果,瀑布流依然是一种很常见的布局方式!!!下面来详细介绍如何实现这种布局.

  • 首先使用的类是UICollectionView
  • 我们要做的是自定义UICollectionViewCell和UICollectionViewLayout
  1. 自定义UICollectionViewCell类,只需要一个UIImageView即可,frame占满整个cell.
  1. 重点是自定义UICollectionViewLayout,注意一定要继承于UICollectionViewLayout,千万别继承于UIColletionViewFlowLayout.
  2. 另外还需要计算图片高度.
  • 为什么要自定义UICollectionViewLayout ?

因为我们需要设置每个item的高度以及位置, 注意这里是位置, 我们真的会设置每个item的位置的相信我!!!自定义UICollectionViewLayout必须要重写三个协议方法,后面会讲到.

  • 为什么要计算图片高度 ?

因为图片宽度固定,所以需要按照图片的比例来计算高度,使图片等比例显示.这样的好处是,妈妈再也不用担心我的照片被拉伸的奇形怪状了...而且还需要用图片的高度来计算整个CollectionView的contentSize...打完收工!!!

  • 主菜来了!!!

以下内容均在自定义的CustomCollectionViewLayout类里边

//自定义UICollectionViewLayout必须要重写的三个协议方法
//1.计算每个item的大小和位置
- (void)prepareLayout;
//2.返回每个item的布局属性
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
//3.返回collectionView的总高度
- (CGSize)collectionViewContentSize;

可以看到第三个方法使用了Nullability和泛型,系统的方法都添加了iOS 9新特性,不了解的朋友可以点击这里来查看iOS 9新特性的说明.

  • 通过上边的三个方法名我们可以大致了解需要去做什么.说一下第二个方法,需要返回一个数组,数组存放布局属性(UICollectionViewLayoutAttributes)对象.那么我们需要写一个属性数组(attributesArray),将布局属性放入这个属性数组并返回.
定义存放布局属性的可变数组
  • 还需要什么呢 ?看了文章开头的朋友应该注意到了,设置瀑布流的列数当然得有个属性(numberOfColumns)来表示列数.
请注意,因为要在外部设置列数,所以这个属性需要写在自定义类的.h文件中
  • 另外为了方便,定义一个属性(itemWidth)来表示item的宽度
  • 再定义一个属性(contentHeight)来表示整个collectionView的contenView的高度
CustomCollectionViewLayout.m
  • 首先是初始化,并没有什么问题
- (instancetype)init {
    self = [super init];
    if (self) {
        _attributesArray = [NSMutableArray array];
        // 默认值设置为2列
        _numberOfColumns = 2;
        _contentHeight = 0.0f;
        _cellMargin = 5.0f;/**< 用来表示间距的属性 */
    }
    return self;
}
  • 然后是getter方法,只需要使用点语法即可得到itemWidth的值(因为它就是固定的)
- (CGFloat)itemWidth {
    //所有边距的和.两列时有三个边距, 三列时有四个边距,逻辑强大就是好...
    CGFloat allMargin = (_numberOfColumns + 1) * _cellMargin;
    //除去边界之后的总宽度
    CGFloat noMarginWidth = CGRectGetWidth(self.collectionView.bounds) - allMargin;
    //出去边距的总宽度除以列数得到每一列的宽度(也就是itemWidth)
    return noMarginWidth / _numberOfColumns;
}

---接下来是难点---

  • 必须重写的第一个方法
- (void)prepareLayout {
    // 定义变量记录高度最小的列,初始为第0列高度最小.
#pragma mark - 注意这个是从0开始算的啊!!!
    NSInteger shortestColumn = 0;
#pragma mark - 注意这个是从0开始算的啊!!!
    // 存储每一列的总高度.因为添加图片的列高度会变,所以需要定义一个数组来记录列的总高度.
    NSMutableArray *columnHeightArray = [NSMutableArray array];
    // 设置列的初始高度为边距的高度,没毛病!!!
    for (int i = 0; i < _numberOfColumns; i++) {
        // 所有列初始高度均设置为cell的间距
        [columnHeightArray addObject:@(_cellMargin)];
    }
    // 遍历collectionView中第 0 区中的所有item
    for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) {
        //需要用到这个玩意,提前拿到.
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 创建系统需要的布局属性对象,看后边的参数就知道这就是每个item的布局属性了
        UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
        // 将布局属性放入数组中,这个数组当然是一开始定义的布局属性数组了
        [_attributesArray addObject:layoutAttributes];
        // 设置每个item的位置(x, y, width, height)
        // 横坐标的起始位置
#pragma mark - 比如一共两列,现在要放一张图片上去,需要放到高度最小的那一列.
#pragma mark - 假设第0列最短,那么item的x坐标就是从一个边距宽度那里开始.
#pragma mark - (itemWidth + cellMargin)为一个整体
        CGFloat x = (self.itemWidth + _cellMargin) * shortestColumn + _cellMargin;
        // 纵坐标就是 总高度数组 中最小列对应的高度
#pragma mark - 图片始终是添加在高度最小的那一列
        CGFloat y = [columnHeightArray[shortestColumn] floatValue];/**<注意类型转换 */
        // 宽度没什么好说的
        CGFloat width = self.itemWidth;
#pragma mark - 这里给自定义的类声明了一个协议,通过协议得到图片的高度,调用时机就是需要item高度的时候
#pragma mark - 将Item的宽度传给代理人(ViewController),VC计算好高度后将高度返回给自定义类
#pragma mark - 也就是↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        CGFloat height = [self.delegate collectionView:self.collectionView
                                                layout:self
                                                 width:self.itemWidth
                              heightForItemAtIndexPath:indexPath];
        // 大功告成,设置item的位置信息,没什么好说
        layoutAttributes.frame = CGRectMake(x, y, width, height);
        // 上边废了半天劲放了一个item上去了,总高度数组是不是该更新一下数据了
        columnHeightArray[shortestColumn] = @([columnHeightArray[shortestColumn] floatValue] + height + _cellMargin);
        // 整个内容的高度,通过比较得到较大值作为整个内容的高度
        self.contentHeight = MAX(self.contentHeight, [columnHeightArray[shortestColumn] floatValue]);
        // 刚才放了一个item上去,那么此时此刻哪一列的高度比较低呢
        for (int i = 0; i < _numberOfColumns; i++) {
            //当前列的高度(刚才添加item的那一列)
            CGFloat currentHeight = [columnHeightArray[shortestColumn] floatValue];
            // 取出第i列中存放列高度
            CGFloat height = [columnHeightArray[i] floatValue];
            if (currentHeight > height) {
                //第i列高度(height)最低时,高度最低的列(shortestColumn)当然就是第i列了
                shortestColumn = i;
            }
        }
    }
// 思考下只使用上边的代码会出现什么问题
// 并不能影响心情,请不要恐慌...
// 提示:这个方法会被多次调用,数组
}

---难点已经结束---

没有理解的朋友请重点看上边的难点方法.

  • 必须重写的第二个方法
// 2.返回的是, 每个item对应的布局属性
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return _attributesArray;
}
  • 必须重写的第三个方法
// 3.返回CollectionView的滚动范围
- (CGSize)collectionViewContentSize {
    return CGSizeMake(0, _contentHeight);
}
  • ViewController里边关于CollectionView的创建和协议方法就没什么好说的了.
  • 看下自定义类CustomCollectionViewLayout的创建以及属性的赋值情况:
CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] init];
layout.numberOfColumns = 2;/**< 在ViewController里设置瀑布流的列数,2列或3列为最佳 */
layout.delegate = self;/**< 指定VC为计算高度协议方法的代理人 */
  • 再看下协议方法的实现部分(在ViewController.m中实现)
- (CGFloat)collectionView:(UICollectionView *)collectionView
                   layout:(UICollectionViewLayout *)collectionViewLayout
                    width:(CGFloat)width
 heightForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
    UIImage *image = _imagesArray[indexPath.row];
    // 根据传过来的宽度来设置一个合适的矩形, 高度设为CGFLOAT_MAX表示以宽度来计算高度
    CGRect boundingRect = CGRectMake(0, 0, width, CGFLOAT_MAX);
    // 通过系统函数来得到最终的矩形,需要引入头文件
    // #import <AVFoundation/AVFoundation.h>
    CGRect imageCurrentRect = AVMakeRectWithAspectRatioInsideRect(image.size, boundingRect);
    return imageCurrentRect.size.height;
}

到这里呢,瀑布流就算是结束了,有兴趣的朋友可以自己动手试一下.

没明白的话可以在评论区提问

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,864评论 1 22
  • 序言 前段时间开发的时候,需要在tableView上拉的时候实现最底下的cell随着滑动从左边移动出来的效果(淘宝...
    sindri的小巢阅读 10,580评论 18 38
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,753评论 1 92
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,103评论 4 62
  • 原文链接:http://www.yinwang.org/blog-cn/2016/01/18/java/?from...
    lioilwin阅读 249评论 0 1