7、LayoutManager 详解:LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

RecyclerView 之所以强大,很大程度上要归功于 LayoutManager。你可以把它想象成一个「布局指挥官」——它决定了每个 item 怎么摆、怎么滚动、怎么复用。我刚开始接触 RecyclerView 时,以为它和 ListView 差不多,直到我深入研究了 LayoutManager 的源码……嗯,那完全是另一个世界。

说白了,LayoutManager 就是 RecyclerView 的灵魂。没有它,RecyclerView 只是一块空白的画布。今天我就带你逐个剖析三种最常用的 LayoutManager,把我这些年踩过的坑、积累的经验一并分享给你。

核心要点:LayoutManager 负责测量、布局、滚动和回收。你选哪种 LayoutManager,直接决定了列表的视觉表现和性能特征。
LayoutManager 知识体系 LayoutManager LinearLayoutManager GridLayoutManager StaggeredGridLayoutManager 特性 • 垂直/水平滚动 • 支持反向布局 • 支持堆叠(StackFromEnd) 特性 • 固定列数/行数 • 支持跨列(SpanSize) • 网格对齐 特性 • 瀑布流布局 • 不等高 item • 支持反向/间隙策略

7.1 LinearLayoutManager:最常用的线性布局

LinearLayoutManager 是最基础、最常用的 LayoutManager。它把 item 排成一列(垂直)或一行(水平),和传统的 ListView 类似,但灵活得多。

我个人习惯用 LinearLayoutManager 来处理绝大多数列表场景。为什么?因为它简单、高效、可控。你想想看,一个聊天列表、一个通知列表、一个设置页面——这些用 LinearLayoutManager 就够了。

// 最基本的用法
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager

// 水平滚动
val horizontalManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView.layoutManager = horizontalManager

// 从底部开始堆叠(比如聊天界面)
val stackFromEndManager = LinearLayoutManager(this)
stackFromEndManager.stackFromEnd = true
recyclerView.layoutManager = stackFromEndManager
我的经验:做聊天界面时,记得设置 stackFromEnd = true 并配合 reverseLayout = false。这样新消息会出现在底部,而且滚动到底部时不会闪烁。我曾经在这上面吃过亏——没设置 stackFromEnd,结果每次插入新消息,列表都从顶部开始跳,用户体验极差。

LinearLayoutManager 还有一个很实用的方法:findFirstVisibleItemPosition()findLastVisibleItemPosition()。我在做阅读类 App 时,经常用它们来记录用户的阅读位置。

// 获取当前可见的第一个和最后一个 item 位置
val firstPos = layoutManager.findFirstVisibleItemPosition()
val lastPos = layoutManager.findLastVisibleItemPosition()

// 滚动到指定位置
layoutManager.scrollToPosition(10)

// 平滑滚动
layoutManager.smoothScrollToPosition(recyclerView, null, 20)

7.2 GridLayoutManager:网格布局的利器

GridLayoutManager 把列表分成网格,每个 item 占据一个或多个单元格。它继承自 LinearLayoutManager,所以 LinearLayoutManager 的特性它基本都有,只是多了一个「列数」的概念。

说白了,GridLayoutManager 就是 LinearLayoutManager 的「网格版」。你设置一个 spanCount(列数),它就把屏幕水平方向分成这么多列,然后 item 从左到右、从上到下排列。

// 3 列网格
val gridManager = GridLayoutManager(this, 3)
recyclerView.layoutManager = gridManager

// 水平滚动的网格
val horizontalGrid = GridLayoutManager(this, 3, GridLayoutManager.HORIZONTAL, false)
recyclerView.layoutManager = horizontalGrid

GridLayoutManager 最强大的功能是 SpanSizeLookup。它允许某个 item 跨越多列。比如在相册中,第一张图可以占两列,其他图占一列。

val gridManager = GridLayoutManager(this, 4)
gridManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
    override fun getSpanSize(position: Int): Int {
        return if (position == 0) {
            4  // 第一个 item 占满整行
        } else {
            1  // 其他 item 占一列
        }
    }
}
recyclerView.layoutManager = gridManager
注意:SpanSizeLookup 的 getSpanSize 方法会被频繁调用。如果你的列表很长,建议缓存计算结果,或者用 SparseIntArray 来存储。我曾经在一个 1000 条数据的网格中没做缓存,结果滑动时明显卡顿——getSpanSize 被调用了上万次。

7.3 StaggeredGridLayoutManager:瀑布流布局

StaggeredGridLayoutManager 实现的是「瀑布流」效果。每个 item 的高度可以不同,它们像瀑布一样错落有致地排列。这在图片展示类 App 中非常常见——比如 Pinterest、花瓣网。

我记得第一次实现瀑布流时,直接用了 GridLayoutManager,结果图片被强行拉伸成一样高,丑得不行。后来换成 StaggeredGridLayoutManager,效果立马就对了。

// 2 列瀑布流,垂直方向
val staggeredManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recyclerView.layoutManager = staggeredManager

// 3 列水平瀑布流
val horizontalStaggered = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.HORIZONTAL)
recyclerView.layoutManager = horizontalStaggered

使用 StaggeredGridLayoutManager 时,有几个坑需要特别注意:

  • item 高度必须不同——如果所有 item 高度一样,那它和 GridLayoutManager 没区别
  • 位置获取方式不同——不能用 findFirstVisibleItemPosition,要用 findFirstVisibleItemPositions(返回数组)
  • item 动画可能异常——删除或添加 item 时,瀑布流的动画效果有时会「跳一下」
// 获取可见位置(返回数组,因为每列的第一个可能不同)
val firstVisibleItems = IntArray(staggeredManager.spanCount)
staggeredManager.findFirstVisibleItemPositions(firstVisibleItems)

val lastVisibleItems = IntArray(staggeredManager.spanCount)
staggeredManager.findLastVisibleItemPositions(lastVisibleItems)
避坑指南:我曾经在瀑布流中做「加载更多」时,发现新加载的 item 位置错乱。排查了半天,发现是 item 的 id 没有唯一性导致的。StaggeredGridLayoutManager 依赖 item 的稳定 id 来做布局缓存。如果你的 item 没有设置稳定的 id(通过 setHasStableIds(true)),瀑布流在刷新时很容易「抖」一下。

7.4 三种 LayoutManager 的对比

特性 LinearLayoutManager GridLayoutManager StaggeredGridLayoutManager
布局方向 垂直/水平 垂直/水平 垂直/水平
列数/行数 1 列 固定列数 固定列数
item 高度 可不同 可不同(但网格对齐) 可不同(瀑布流)
跨列支持 不适用 支持(SpanSizeLookup) 不支持
性能 最高 较高 一般(布局计算复杂)
适用场景 列表、聊天、设置 相册、商品列表 图片瀑布流、卡片墙

7.5 如何选择?我的建议

选 LayoutManager 其实没那么复杂。我一般按这个思路来:

  • 90% 的场景用 LinearLayoutManager——别想太多,默认用它就对了
  • 需要网格展示时用 GridLayoutManager——比如商品列表、图标网格
  • 只有图片类、且高度差异大时用 StaggeredGridLayoutManager——别滥用,它性能开销大

你想想看,很多 App 其实根本用不到瀑布流。强行用 StaggeredGridLayoutManager 反而会引入不必要的复杂度。我见过一个新闻列表 App,明明每条新闻高度差不多,却用了瀑布流,结果滑动时经常卡顿——这就是典型的「杀鸡用牛刀」。

一个小技巧:如果你不确定用哪种,先用 LinearLayoutManager。后期想改成网格或瀑布流,只需要改一行代码——LayoutManager 的切换对 Adapter 是透明的。这就是 RecyclerView 设计的高明之处。

好了,关于 LayoutManager 的三种实现,我就讲到这里。记住:LinearLayoutManager 是基础,GridLayoutManager 是网格,StaggeredGridLayoutManager 是瀑布流。理解它们的特性和适用场景,你就能在项目中游刃有余了。


公众号:蓝海资料掘金营,微信deep3321