RulerView源码解析之二:为什么卷尺松手会吸附到最近刻度?VelocityTracker+Scroller惯性滑动详解

发布时间:2026/10/24 3:51:01
RulerView源码解析之二:为什么卷尺松手会吸附到最近刻度?VelocityTracker+Scroller惯性滑动详解 【免费下载链接】RulerView卷尺系列控件包含普通卷尺如体重、金额尺、时间尺项目地址https://gitcode.com/gh_mirrors/ru/RulerView点击查看免费下载RulerView 是一个 Android 卷尺系列控件包含普通卷尺体重尺、金额尺MoneySelectRuleView和时间尺TimeRuleView三种控件被大量开发者用来做体重选择、金额选择、时间选择等场景。本文是源码解析第二篇带你彻底弄懂一个核心问题为什么卷尺松手后会自动吸附到最近的刻度线它背后正是 Android 自定义 View 惯性滑动的经典组合——VelocityTracker 追踪滑动速度 Scroller 驱动惯性滚动最后用Math.round完成磁吸对齐。读完这篇文章你也能给自己的自定义控件加上同样顺滑的惯性滑动体验。先看下 demo 里实际的效果感受这个松手即吸附的顺滑度一、先搞懂整体流程松手后发生了什么很多新手看到惯性滑动四个字就绕晕了其实整个流程可以拆成一条清晰的时间线阶段发生了什么由谁负责① 手指按住滑动每收到一个触摸点就记录下来VelocityTracker② 手指离开ACTION_UP根据记录算出离开瞬间的速度VelocityTracker③ 速度 ≥ 阈值卷尺以这个速度甩出去逐渐减速Scroller.fling()④ 速度 阈值不走惯性直接平滑对齐到最近刻度scrollToGradation()⑤ 惯性滚动到终点最后再吸附一次保证停在刻度线上computeScroll()一句话总结速度够快就甩速度不够就贴无论哪种情况最终都会落在整数刻度上。三个关键常量在构造函数里就取自系统保证和系统控件如 ViewPager手感一致见 RuleView.javaViewConfiguration viewConfiguration ViewConfiguration.get(context); TOUCH_SLOP viewConfiguration.getScaledTouchSlop(); // 滑动阈值 MIN_FLING_VELOCITY viewConfiguration.getScaledMinimumFlingVelocity(); // 惯性最小速度 MAX_FLING_VELOCITY viewConfiguration.getScaledMaximumFlingVelocity(); // 惯性最大速度TOUCH_SLOP是误触保护手指按下后位移小于这个值视为点击而非滑动避免手抖导致刻度乱跳。二、关键步骤详解步骤1VelocityTracker 持续收集触摸点速度不是一次算出来的而是靠积累出来的。onTouchEvent()一进来就先干两件事获取跟踪器懒加载然后把当前触摸点喂给它见 RuleView.java#L316-L325if (mVelocityTracker null) { mVelocityTracker VelocityTracker.obtain(); } mVelocityTracker.addMovement(event);obtain()从对象池取实例VelocityTracker 是重量级对象用完应recycle()addMovement(event)把每个触摸事件都记下来内部维护一个带时间戳的点序列可以把它理解成行车记录仪它不关心你现在滑到哪只负责把每个时刻你在哪都记下来。步骤2ACTION_UP 时计算速度并做二选一判断手指抬起的瞬间才是算速度的时机。核心代码见 RuleView.java#L347-L361case MotionEvent.ACTION_UP: // 计算速度使用1000ms为单位 mVelocityTracker.computeCurrentVelocity(1000, MAX_FLING_VELOCITY); // 获取速度。速度有方向性水平方向左滑为负右滑为正 int xVelocity (int) mVelocityTracker.getXVelocity(); // 达到速度则惯性滑动否则缓慢滑动到刻度 if (Math.abs(xVelocity) MIN_FLING_VELOCITY) { // 速度具有方向性需要取反 mScroller.fling((int) mCurrentDistance, 0, -xVelocity, 0, 0, (int) mNumberRangeDistance, 0, 0); invalidate(); } else { scrollToGradation(); } break;这里有 4 个新手极易踩坑的细节computeCurrentVelocity(1000)参数1000表示速度单位是 px/s默认是 px/1000ms换算成每秒速度要乘 10。getXVelocity()有正负右滑为正、左滑为负这是手指的速度不是刻度移动的速度。fling 时速度取反-xVelocity手指向右滑刻度应该向左走就像用手指推尺子两者方向天然相反所以必须取反。这是源码注释里专门强调的点。MIN_FLING_VELOCITY是分流开关慢速松手说明用户只是微调了一下此时甩出去反而体验很差直接吸附scrollToGradation()更合理。步骤3Scroller.fling —— 让卷尺自己跑起来fling()是 Scroller 的精髓给它一个初速度和滑动范围它内部用减速曲线算出每一帧应该停在哪个位置但注意——Scroller 自己不会触发重绘它只负责算位置真正的动起来靠的是每帧回调。fling的参数含义对应上面代码参数传值含义startX(int) mCurrentDistance当前已滚动的距离起点startY0只用水平方向忽略velocityX-xVelocity初速度取反后minX / maxX0/mNumberRangeDistance滑动范围防止甩出最大值/最小值mCurrentDistance是当前值距最小值的像素距离它是整个控件的唯一真实数据源所有数值都由它反推数值×10转int防浮点精度问题的思路见 convertValue2Number()。步骤4computeScroll() 逐帧驱动 终点吸附惯性滚动期间系统每帧会回调computeScroll()这是动画驱动的标准写法见 RuleView.java#L402-L412Override public void computeScroll() { if (mScroller.computeScrollOffset()) { // 还没滑完返回true if (mScroller.getCurrX() ! mScroller.getFinalX()) { mCurrentDistance mScroller.getCurrX(); // 更新当前距离 calculateValue(); // 重算数值 触发重绘 } else { scrollToGradation(); // 到达终点吸附到刻度 } } }流程是computeScrollOffset()返回 true 说明还在滚动 → 取本帧应处的位置getCurrX()更新mCurrentDistance→ 重算数值并invalidate()触发重绘 →当getCurrX()追上getFinalX()到达终点时调用scrollToGradation()做最后一次吸附。吸附的核心一行 Math.round为什么松手会吸附到最近刻度的答案浓缩在 scrollToGradation() 里private void scrollToGradation() { // 距离 ÷ 刻度间距四舍五入得到最近的第几格再乘回去 mCurrentNumber mMinNumber Math.round(mCurrentDistance / gradationGap) * mNumberUnit; mCurrentNumber Math.min(Math.max(mCurrentNumber, mMinNumber), mMaxNumber); // 边界保护 mCurrentDistance (mCurrentNumber - mMinNumber) / mNumberUnit * gradationGap; currentValue mCurrentNumber / 10f; ... }原理非常朴素当前距离 ÷ 一格刻度宽度 当前在第几格小数Math.round四舍五入 最近整数格再乘回去 最近刻度的精确位置。这就完成了磁吸。之后再Math.min/Math.max夹在[min, max]范围内保证极端甩动也不会越界。 注意这里吸附是直接跳变到目标刻度而非再做一段动画因为 fling 终点本来就离目标刻度很近直接对齐视觉上是落定体验反而更干脆。别忘了手指再次按下要急停如果惯性还在滚动时用户又按下了控件必须立刻终止动画否则会出现手指跟手了但画面还在自动走的错乱。ACTION_DOWN分支里一行代码解决见 RuleView.java#L326-L330case MotionEvent.ACTION_DOWN: mScroller.forceFinished(true); // 强制结束惯性滚动 mDownX x; isMoved false; break;外部调用setCurrentValue()跳转数值时同样会先forceFinished(true)再启动新的平滑滚动见 setCurrentValue()避免两套滚动叠加打架。三、另外两个控件同套路小差异这套VelocityTracker Scroller 吸附的骨架在另外两个控件里完整复用但细节上各有取舍。MoneySelectRuleView金额尺吸附逻辑与 RuleView 一致——Math.round(mCurrentDistance / unitGap) * valueUnit对齐到百元整数刻度见 scrollToGradation()。它额外处理了ACTION_CANCEL场景触摸序列被系统打断如来电弹窗时用abortAnimation()结束动画并直接把数值置为终点值见 onTouchEvent()比单纯forceFinished更严谨。TimeRuleView时间尺支持惯性滑动fling逻辑完全相同见 onTouchEvent()但它的computeScroll()里没有再做吸附——因为时间尺最小单位是秒级、数值精度本身就足够高吸附没有意义反而需要秒级精确定位见 computeScroll()。这也提醒我们吸附不是必须的是否吸附取决于你的业务是否需要档位概念。四、要点清单建议收藏VelocityTracker 只记不算addMovement()持续喂点computeCurrentVelocity(1000)在 ACTION_UP 时才算出 px/s 速度。速度有方向手指速度与刻度移动方向相反fling时必须传-xVelocity。MIN_FLING_VELOCITY 是分流器快速→fling 惯性甩出慢速→直接吸附两条路殊途同归。Scroller 只算不画真正驱动靠computeScroll()逐帧取getCurrX()并invalidate()。吸附就是一行Math.round距离 ÷ 刻度间距四舍五入再乘回配合 min/max 边界保护。ACTION_DOWN 必须forceFinished(true)中断惯性动画保证重新跟手。阈值取系统默认ViewConfiguration手感与系统控件保持一致。这套模式几乎是 Android 所有可滑动选择器的通用解法——体重秤、金额输入、日历滚轮、日期选择器本质上都是同一套 VelocityTracker Scroller 吸附公式的变体。理解了它你就拿到了自定义滑动控件的万能钥匙 。相关源码速查核心控件源码RuleView.java金额尺源码MoneySelectRuleView.java时间尺源码TimeRuleView.java自定义属性定义attrs.xml使用与属性说明README.md赞分享【免费下载链接】RulerView卷尺系列控件包含普通卷尺如体重、金额尺、时间尺项目地址https://gitcode.com/gh_mirrors/ru/RulerView点击查看免费下载相关推荐Buzz 字幕调整指南把过短或过长的字幕快速改到合适长度Buzz 字幕调整指南把过短或过长的字幕快速改到合适长度 刚导出的字幕文件经常是这样的一行只有两三个字或者一句话被挤得超长贴到播放器里根本没法看。Buz语音AI 应用本地部署桌面应用为什么SFFocusViewLayout滑动如此丝滑UICollectionViewLayout核心机制深度解析为什么SFFocusViewLayout滑动如此丝滑UICollectionViewLayout核心机制深度解析 你有没有在 iOS 应用里刷到过这种列表中Qwen3.8-27B-AP-GGUF完整解析:为什么它是同尺寸下最接近Qwen原版的GGUF量化包Qwen3.8 27B AP GGUF完整解析:为什么它是同尺寸下最接近Qwen原版的GGUF量化包 Qwen3.8 27B AP GGUF 是开源模型 Qwe人工智能大模型模型量化本地部署上一篇LX Music桌面版免费的跨平台音乐聚合播放器下一篇魔兽争霸3终极兼容性指南5分钟解决经典游戏在现代系统的所有问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询