news 2026/8/27 16:22:44

material-scrolling源码精读:RecyclerViewHolder如何桥接ObservableRecyclerView滚动状态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
material-scrolling源码精读:RecyclerViewHolder如何桥接ObservableRecyclerView滚动状态

material-scrolling源码精读:RecyclerViewHolder如何桥接ObservableRecyclerView滚动状态

【免费下载链接】material-scrollingAndroid library for material scrolling techniques.项目地址: https://gitcode.com/gh_mirrors/ma/material-scrolling

material-scrolling 是一款 Android Material 滚动技术方案库,把 Google 设计规范的滚动交互变成几行 API。本文带你逐段精读它的核心源码:RecyclerViewHolder究竟如何桥接ObservableRecyclerView的滚动状态,把一次触摸手势变成标题平移、背景视差、FAB 缩放的完整链路。

一句话总览:ObservableRecyclerView 感知手势 → RecyclerViewHolder 归一化坐标 → BehaviorDispatcher 广播 → Behavior 们各自动画

模块导航:先看地图再读代码 🗺️

模块文件角色
library/src/main/java/jp/satorufujiwara/scrolling/RecyclerViewHolder.java本文主角:滚动状态桥接器
library/src/main/java/jp/satorufujiwara/scrolling/MaterialScrollingLayout.java容器布局,负责"接线"
library/src/main/java/jp/satorufujiwara/scrolling/MaterialScrollingViewPager.javaViewPager 多页联动版本
library/src/main/java/jp/satorufujiwara/scrolling/BehaviorDispatcher.java事件广播中心
library/src/main/java/jp/satorufujiwara/scrolling/Behavior.java所有滚动行为的抽象基类
library/src/main/java/jp/satorufujiwara/scrolling/Utils.java递归查找 ObservableRecyclerView 的工具
sample/src/main/java/jp/satorufujiwara/scrolling/sample/MainActivity.java示例入口,看真实用法

想看完整演示?README 在arts/目录下附了三段 GIF(viewpager.gifimagefab.gifviewpager_land.gif),分别展示横向翻页、大图 FAB、横屏效果。

核心机制:滚动事件如何"过桥"

RecyclerView 本身只回调"我滚动了",不关心页面上其他 View 怎么动。material-scrolling 的答案是中间层桥接

触摸手势 │ ▼ ObservableRecyclerView ── 注册回调 ──▶ RecyclerViewHolder (桥) │ 归一化坐标 scrollY + 增量 dy │ 支持开关 / 缓存 / 同步滚动 ▼ BehaviorDispatcher (广播) │ ┌────────────────────────┼────────────────────┐ ▼ ▼ ▼ ScrollBehavior ParallaxBehavior FabBehavior (标题跟手平移) (背景视差偏移) (FAB 缩放消失)

逐段精读:RecyclerViewHolder 的 7 个关键设计

以下路径均相对仓库根目录,核心文件为 library/src/main/java/jp/satorufujiwara/scrolling/RecyclerViewHolder.java。

1️⃣ 构造即接线:两行代码完成桥接

this.recyclerView = recyclerView; this.recyclerView.setClipToPadding(false); recyclerView.setScrollViewCallbacks(new OnScrollDelegate(this));
  • 持有ObservableRecyclerView引用,并把回调"插线"到内部类OnScrollDelegate(第 72–94 行)——它只干一件事:把onScrollChangedonDownMotionEventonUpOrCancelMotionEvent三个事件原样转发给 holder 自己。
  • setClipToPadding(false):允许列表内容画进 padding 区,顶部大图的视觉空间由此而来。

2️⃣ flexibleHeight:让 RecyclerView "自带头部留白"

setFlexibleHeight()(第 28–31 行)把布局属性ms_flexible_height转成RecyclerView 顶部 padding

app:ms_flexible_height="240dp"

于是"标题区"不再是一个独立 View,而是列表自带的留白区域——这是整个方案的坐标系基准。

3️⃣ 坐标归一化:把"列表内部位置"换算成"页面滚动量"

getScrollY()(第 37–39 行)是桥的核心:

return isDirty ? scrollY : recyclerView.getCurrentScrollY() + flexibleHeight;
  • 实时位置 =getCurrentScrollY() + flexibleHeight,这样 scrollY 在"大图完全收起"时恰好等于 flexibleHeight,后续所有 Behavior 都用同一把尺子;
  • isDirty标志:当scrollTo()主动滚动后,回调还没来得及回来,就优先返回缓存值,保证 ViewPager 同步多页时坐标不穿帮(见第 4 节)。

4️⃣ 增量事件:一次滚动只发"差值"

void onScrolled(final int y) { isDirty = false; if (!isDispatchScroll) return; scrollY = y + flexibleHeight; behaviorDispatcher.onScrolled(scrollY, scrollY - preScrollY); preScrollY = scrollY; }

发给 Behavior 的是(scrollY, dy)两个参数——绝对位置 + 本次增量。dy让行为可以写"跟手回弹""惯性感知"这类依赖速度的效果。

5️⃣ 主动滚动:scrollTo 的反向桥

isDirty = true; scrollY = y; final LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); lm.scrollToPositionWithOffset(0, -y);

桥是双向的:MaterialScrollingViewPager翻页时会用holder.scrollTo(...)非当前页同步到当前页位置(见 MaterialScrollingViewPager.java 的onPageScrollStateChanged),这就是多页"齐刷刷一起收起"的效果来源。

6️⃣ 事件开关:setIsDispatchScroll

MaterialScrollingLayoutaddView时创建 holder、removeView时关闭开关(MaterialScrollingLayout.java 第 50–67 行)。ViewPager 场景更讲究:onPageSelected时先关闭所有 holder 的开关,只打开当前页那个——彻底杜绝旧页残留回调污染动画。

7️⃣ 手势生命周期:按下 / 抬起同样被桥接

onDownMotionEventonUpOrCancelMotionEvent(ScrollState)也全部透传给BehaviorDispatcher。Behavior 拿到ScrollState(FLING / DRAG / IDLE)就能做"抬手后回弹"的决策,桥接的不仅是位置,还有手势语义

三个最容易踩坑的细节 ⚠️

  1. isDirty不是脏检查:它标记的是"我刚执行过主动scrollTo,回调值不可信",读源码时别按字面理解。
  2. 开关先于缓存setIsDispatchScroll(false)只拦事件,getScrollY()缓存值仍然有效,两者职责分离。
  3. 坐标系偏移:所有 Behavior 收到的 scrollY 都已+ flexibleHeight,写自定义行为时直接用getFlexibleHeight()做上限即可,例如内置的 ScrollBehavior.java:
ViewCompat.setTranslationY(target, -Math.min(scrollY, getFlexibleHeight()));

上手只需两步 🚀

第一步,布局里包一层容器并声明 flexible 区域(见 library/src/main/res/values/attrs.xml 定义的属性):

<jp.satorufujiwara.scrolling.MaterialScrollingLayout app:ms_flexible_height="240dp"> <com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView android:layout_width="match_parent" android:layout_height="match_parent"/> </jp.satorufujiwara.scrolling.MaterialScrollingLayout>

第二步,代码里注册行为,一行一个效果:

materialScrollingLayout.addBehavior(bgImageView, new ParallaxBehavior()); materialScrollingLayout.addBehavior(titleTextView, new ScrollingBehavior());

内置行为都在 library/src/main/java/jp/satorufujiwara/scrolling/behavior/ 下,自定义只需继承 Behavior.java 重写onScrolled,范例参考 sample 的 FabBehavior 与 TitleBehavior。

依赖(jcenter):jp.satorufujiwara:material-scrolling:1.2.1+com.github.ksoichiro:android-observablescrollview:1.6.0

总结:这张"桥"值得抄的设计

  • 归一化:把"列表内部滚动"统一换算成"页面滚动量",一套坐标打天下;
  • 可插拔:回调、开关、缓存三个正交机制,让单页布局和多页 ViewPager 共用同一个 holder;
  • 广播式:一个 Dispatcher 一对多分发,Behavior 之间零耦合。

想动手验证,先 clone 仓库https://gitcode.com/gh_mirrors/ma/material-scrolling,跑 sample 工程,对照本文的 7 个设计点逐行读,你会发现不到 100 行的RecyclerViewHolder才是整套 Material 滚动魔法的引擎。

【免费下载链接】material-scrollingAndroid library for material scrolling techniques.项目地址: https://gitcode.com/gh_mirrors/ma/material-scrolling

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 16:16:28

灰色预测GM(1,1)模型:小样本时间序列预测的数学建模利器

1. 从“拍脑袋”到“有章法”&#xff1a;为什么数模竞赛里灰色预测是块宝&#xff1f;搞数学建模的朋友&#xff0c;尤其是刚入门的新手&#xff0c;估计都遇到过这种尴尬&#xff1a;题目给了一串时间序列数据&#xff0c;让你预测未来趋势。数据量不大&#xff0c;历史信息也…

作者头像 李华
网站建设 2026/8/27 16:15:44

基于springboot的英语课程教学管理系统毕业设计项目源码

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华