Appearance
android-nested-scroll-coordination-demo
1. 实验目标
围绕“折叠头部 + 列表继续滚”这条主线,同时验证 Android 的两套完整实现:
- Compose:
Modifier.nestedScroll()+NestedScrollConnection - Android View:
CoordinatorLayout + AppBarLayout + RecyclerView
核心不是比较谁代码短,而是看清:
- Compose 侧是显式位移消费链
- View 侧是容器 behavior 编排
- 两者都在解决“共享滚动位移”的同一类问题
2. 原理与思路
2.1 Compose 为什么像“位移分账”
Compose 会在滚动前后把可用位移、已消费位移、剩余位移显式交给 NestedScrollConnection。你要自己决定:
- 头部先吃多少
- 列表再吃多少
- 边界时父级要不要补消费
2.2 Android View 为什么像“容器行为编排”
传统 View 体系把 nested scrolling parent/child 协议藏在容器与 behavior 里:
AppBarLayout知道自己能折叠RecyclerView把滚动继续交给AppBarLayout或自己消费CoordinatorLayout负责把行为连接起来
开发时经常会觉得“没写多少逻辑就能跑”,但问题是调试时很难看到每一段位移到底被谁吃掉了。
2.3 方案对比矩阵
| 方案 | 通俗比喻 | 底层原理 | 保证特性 | 吞吐性能 | 运行开销 | 适用场景 |
|---|---|---|---|---|---|---|
| Compose NestedScroll | 自己拿着账本给每一方分位移 | pre/post scroll + fling 回调 | 位移消费顺序显式可控 | 高 | 中 | Compose 页面、复杂局部联动 |
| View CoordinatorLayout | 让调度员按既定规则分派滚动 | nested scrolling parent/child + behavior | 经典折叠头、吸顶联动稳定 | 高 | 低到中 | 旧页面、XML 体系、迁移阶段 |
| Flutter NestedScrollView | 让 Sliver 结构统一协调 outer/inner scroll | outer/inner ScrollPosition + Sliver 协同 | 头部折叠 + 内部列表稳定 | 高 | 中 | Flutter 页面主方案 |
3. Android / 移动端实战场景与误配事故
3.1 实战场景
- 个人主页折叠头图
- 搜索页吸顶过滤栏
- 商品详情头图折叠 + 详情列表
- BottomSheet 内部长列表
3.2 常见误配与事故注意事项
- 直接给外层套
ScrollView再把RecyclerView塞进去 - Compose 顶层反复读取 offset,滚一下整页重组
- Flutter 用
ScrollController强行监听,却没把需求落回 Sliver 结构
3.3 排障思路
- 先问是不是“共享位移”问题,而不是普通列表题
- 再确认滚动主语是谁:头部、列表、sheet 还是外层容器
- 再看边界:谁先吃,谁后吃,滚不动后谁接手
- 最后才看 API 选型
4. 实验源码与运行验证
关键源码
关键参数 / 开关
maxHeaderOffsetPx:Compose 头部最多折叠多少像素Compose / ViewTab:切换两种实现,便于对照折叠行为manual offset label:观察头部当前折叠量与列表首项位置
运行方式
bash
cd labs/android/host
./gradlew assembleDebug安装后打开 Android Host → 打开 Android 嵌套滚动协同实验室。
预期现象
- Compose Tab:先折叠头部,再滚列表
- View Tab:
AppBarLayout先收起,再由RecyclerView继续滚 - 两边都能观察到“头部先吃,列表后吃”的一致结果
5. 对应知识库文档
- 理论主文档:Jetpack Compose 嵌套滚动与滚动协同
- 状态分层总览:状态管理、路由、架构