ios小红书的瀑布流
小红书的瀑布流是其最具代表性的 UI 特征之一,它不仅仅是一种布局方式,更是其产品体验的核心,下面我将从 设计理念、技术实现、性能优化和用户体验 四个方面来详细解析。
设计理念与用户体验
在谈论技术之前,首先要理解小红书为什么要用瀑布流。
- 最大化信息密度: 在有限的屏幕空间内,瀑布流可以展示比传统的网格布局(如 Instagram)更多的内容,它利用了屏幕高度,让用户可以快速浏览大量帖子。
- 视觉吸引力: 每个卡片的高度不一,错落有致,打破了规整布局的单调感,更像一本杂志或灵感墙,具有很强的视觉冲击力,能激发用户的浏览兴趣。
- “发现”式体验: 瀑布流非常适合展示图文混排的内容,用户在上下滑动时,会被不同尺寸、不同内容的图片和标题所吸引,这种“所见即所得”的探索感,非常符合小红书“标记我的生活”和“发现美好生活”的定位。
- 内容驱动: 布局完全服务于内容,一张高质量的竖版图片会占据更大的空间,获得更多展示;而一张横版图片或包含大量文字的笔记则会自动调整高度,确保所有信息都能被完整看到。
技术实现方案
在 iOS 上实现瀑布流,主要有三种主流方案,各有优劣。
使用 UICollectionView 的 UICollectionViewFlowLayout(最推荐)
这是苹果官方提供的、最标准、最强大的实现方式,也是绝大多数 App(包括小红书)的首选。
核心原理:
UICollectionView 通过其数据源方法 collectionView(_:layout:sizeForItemAt:) 来动态计算每个 UICollectionViewCell 的大小。
实现步骤:
-
创建
UICollectionView和UICollectionViewFlowLayout:let collectionView = UICollectionView(frame: .zero, collectionViewLayout: CustomFlowLayout()) collectionView.register(NoteCell.self, forCellWithReuseIdentifier: "NoteCell")
-
自定义
UICollectionViewFlowLayout: 这是实现瀑布流的关键,你需要重写UICollectionViewFlowLayout的几个核心方法。class CustomFlowLayout: UICollectionViewFlowLayout { // 定义列数,比如2列 private let numberOfColumns = 2 // 重写此方法来计算每个 item 的尺寸 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { // 先调用父类方法获取默认属性 let attributes = super.layoutAttributesForElements(in: rect) guard attributes != nil else { return nil } // 创建一个数组来记录每一列的当前 Y 坐标(累加高度) var columnHeights = [CGFloat](repeating: 0, count: numberOfColumns) // 遍历所有属性 for attributes in attributes! { if attributes.representedElementCategory == .cell { // 找出当前高度最小的那一列 let minHeight = columnHeights.min()! let columnIndex = columnHeights.firstIndex(of: minHeight)! // 计算 item 的 x, y 坐标 let x = CGFloat(columnIndex) * (itemSize.width + minimumInteritemSpacing) let y = minHeight + minimumLineSpacing // 更新布局属性 attributes.frame = CGRect(x: x, y: y, width: itemSize.width, height: 0) // 高度先设为0,下面会计算 // 更新该列的高度 columnHeights[columnIndex] = attributes.frame.maxY } } return attributes } // 重写此方法来返回 content size override var collectionViewContentSize: CGSize { // 找出所有列中最高的那一列的高度 let maxHeight = columnHeights.max() ?? 0 return CGSize(width: collectionView!.bounds.width, height: maxHeight) } } -
在
UICollectionViewDataSource中提供数据: 在cellForItemAt中,根据数据模型配置 Cell 的内容,在sizeForItemAt中返回一个估算的宽度,高度设为 0 或一个估算值。(图片来源网络,侵删)func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NoteCell", for: indexPath) as! NoteCell let note = notes[indexPath.item] cell.configure(with: note) // 配置 cell 的图片和文字 return cell } // 关键:返回 Cell 的估算尺寸 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let note = notes[indexPath.item] let width = (collectionView.bounds.width - (numberOfColumns - 1) * spacing) / numberOfColumns // 这里可以做一个简单的估算,比如根据图片的宽高比计算 // 或者直接返回一个固定宽度,高度在 layoutAttributesForElements 中动态计算 // 更常见的做法是:在 sizeForItemAt 中只返回宽度,高度为0,然后在 CustomFlowLayout 的 prepare() 或 layoutAttributesForElements 中根据内容计算高度。 // 为了性能,通常在 sizeForItemAt 中计算一个近似高度 let estimatedHeight = calculateHeight(for: note, width: width) return CGSize(width: width, height: estimatedHeight) }
优点:
- 性能优异:
UICollectionView是为高性能滚动而生的原生组件。 - 功能强大: 支持复用、动画、多选、分区等复杂功能。
- 官方支持: 稳定可靠,有完善的文档和社区支持。
缺点:
- 实现复杂: 需要理解
UICollectionViewLayout的工作原理,实现瀑布流逻辑相对繁琐。
使用第三方库(如 IGListKit)
IGListKit 是 Instagram 开源的一个数据驱动、模块化的 UI 框架,它内部也基于 UICollectionView,但提供了更高级的抽象。
实现原理:
它将数据源和 UI 分离,通过 ListAdapter 和 SectionController 来管理,每个瀑布流都可以看作一个 SectionController。
优点:
- 架构清晰: 数据和 UI 解耦,代码结构更清晰,适合大型复杂项目。
- 高性能: 同样基于
UICollectionView,性能有保障。 - 易于扩展: 添加新的数据类型或布局非常方便。
缺点:
- 学习成本: 需要学习
IGListKit自己的一套架构和数据绑定方式。 - 增加依赖: 引入第三方库会增加项目的体积和维护成本。
使用 UITableView(不推荐,但可行)
早期一些应用会使用 UITableView 来模拟瀑布流。
实现原理:
- 单列瀑布流: 每一行就是一个 Cell,高度根据内容动态计算。
- 多列瀑布流: 创建多个
UITableView并排,每个UITableView负责一列,通过代理方法tableView(_:heightForRowAt:)计算每个 Cell 的高度,并同步滚动。
优点:
- 实现简单,特别是对于单列情况。
缺点:
- 性能差: 无法像
UICollectionView那样高效地复用 Cell,内存占用高,滚动流畅度远不如UICollectionView。 - 功能受限: 实现多列同步滚动、动画等非常复杂。
- 架构混乱: 违背了
UITableView的设计初衷。
除非是极其简单的场景,否则在现代 iOS 开发中,应避免使用 UITableView 实现瀑布流。
性能优化(关键)
小红书的瀑布流有成千上万条内容,性能优化至关重要。
-
图片加载与缓存:
- 异步加载: 必须在后台线程(如
GCD或OperationQueue)下载图片。 - 内存缓存: 使用
NSCache或第三方库(如Kingfisher,SDWebImage)的内存缓存,避免重复下载。 - 磁盘缓存: 将图片缓存到本地,避免重复网络请求,加快二次加载速度。
- 图片解码: 避免主线程进行图片解码,防止卡顿。
SDWebImage等库已经处理了这一点。
- 异步加载: 必须在后台线程(如
-
Cell 高度计算:
- 避免主线程计算: Cell 的高度计算(特别是图文混排的高度)是比较耗时的操作,应该在后台线程(如
GlobalQueue)中计算好高度,再更新到主线程。 - 估算高度 vs. 精确高度:
- 估算高度: 在
sizeForItemAt中返回一个估算值(如 200pt),可以让UICollectionView快速计算出初始布局,实现“所见即所得”的流畅滚动,这是 iOS 10+ 的self-sizing cell的优势。 - 精确高度: 当 Cell 实际渲染出来后,再通过代理方法
collectionView(_:layout:didEndDisplaying:forItemAt:)或 KVO 获取到精确高度,并更新数据源,这样下次滚动回来时,就能显示精确的尺寸。
- 估算高度: 在
- 缓存高度: 将计算好的 Cell 高度缓存起来(例如用
NSCache或一个字典),避免重复计算。
- 避免主线程计算: Cell 的高度计算(特别是图文混排的高度)是比较耗时的操作,应该在后台线程(如
-
数据加载策略:
- 分页加载: 每次只请求和渲染一屏或两屏的数据,滚动到底部时再加载下一页。
- 预加载: 在用户即将滚动到底部之前,提前发起下一页数据的网络请求,无缝衔接,提升用户体验。
-
复用与布局优化:
- 确保 Cell 复用:
UICollectionView的复用机制是其高性能的核心,要确保cellForItemAt逻辑正确。 - 避免在
layoutSubviews中做复杂计算:layoutSubviews会被频繁调用,里面的逻辑要尽量轻量。
- 确保 Cell 复用:
小红书瀑布流的细节
小红书的瀑布流之所以体验好,除了上述技术点,还有很多细节打磨:
- 动态宽度与固定列数: 通常采用固定的列数(如2列或3列,根据屏幕宽度自适应),Cell 的宽度是动态计算的,高度则根据内容自适应。
- 圆角与阴影: 每个 Cell 都有圆角和轻微的阴影,增加了卡片的立体感和精致感。
- 渐进式加载: 图片会先显示一个低分辨率的占位图,然后清晰度逐渐提升,而不是等待全部加载完成。
- 吸顶/悬浮效果: 当用户滚动时,顶部的搜索栏或分类栏可能会固定在屏幕顶部,方便用户随时操作,这可以通过
UIScrollView的contentOffset和UIVisualEffectView来实现。 - 无限滚动: 用户滚动到底部时,会自动加载更多内容,形成一种“刷不完”的感觉。
| 特性/方案 | UICollectionViewFlowLayout (推荐) |
UITableView (不推荐) |
第三方库 |
|---|---|---|---|
| 性能 | 极高 | 较低 | 高 |
| 实现复杂度 | 中等 | 简单 | 中等 (需学习新框架) |
| 功能丰富度 | 非常丰富 | 有限 | 非常丰富 |
| 架构灵活性 | 一般 | 差 | 极高 |
| 适用场景 | 绝大多数瀑布流场景 | 简单单列列表 | 大型、复杂、模块化项目 |
对于 iOS 掌握使用 UICollectionView 和自定义 UICollectionViewFlowLayout 来实现瀑布流是一项必备的核心技能,它不仅能让你的应用拥有类似小红书那样优秀的视觉效果,更能保证其在海量数据下的流畅性能。
作者:99ANYc3cd6本文地址:https://www.chumoping.net/post/7077.html发布于 01-05
文章转载或复制请以超链接形式并注明出处初梦运营网



