调用SmoothScrollTo(0,0)时如何停止NestedScrollView的滚动动画
NestedScrollView 滚动残留问题解决方案
问题根因
NestedScrollView 的 fling 滚动由内部私有Scroller组件驱动,所有滚动任务会通过postOnAnimation持续投递执行。如果没有主动终止这个正在运行的Scroller任务流,直接启动新的平滑滚动动画,就会出现新旧动画并行、原有动画残留一小段时间才停止的现象。之前的同类公开方案失效,本质是没有从任务队列层面清空所有待执行的滚动回调,仅靠标记位判断无法中断Scroller的执行流程。
落地修正步骤
- 在自定义
ObservableNestedScrollView中,优先调用父类提供的StopScroll()方法彻底终止当前正在运行的所有滚动动画。该方法会直接强制Scroller结束当前滚动流程,清空所有待执行的滚动回调,从根源上消除动画残留。 - 修改全局滚动到顶部的触发逻辑,严格遵循「先终止原有动画,再启动新动画」的执行顺序:
public void ScrollToTop() { if (!IsOnTopOfScroll()) { // 必须在启动新动画前执行,清空所有正在进行的滚动任务 scrollView.StopScroll(); ObjectAnimator.OfInt(scrollView, "scrollY", 0) .SetDuration(150) .Start(); } } - 修正自定义View内的自动滚动标记位逻辑:
原有逻辑在OnScrollChanged中只要滚动差值小于1px就复位isAutoScrolling标记,触发时机过早,容易导致新滚动动画还未执行完成就被触摸事件拦截。调整为只有滚动位置真正到达边界时才复位标记:public override void ScrollTo(int x, int y) { // 强制终止原有滚动任务后再执行位置设置 StopScroll(); isAutoScrolling = true; base.ScrollTo(x, y); } protected override void OnScrollChanged(int x, int y, int oldX, int oldY) { base.OnScrollChanged(x, y, oldX, oldY); if (isAutoScrolling) { // 到达顶部或底部边界时,再复位自动滚动标记 int scrollRange = ComputeVerticalScrollRange(); if (y == 0 || y >= scrollRange) { isAutoScrolling = false; } } // 保留原有滚动事件分发逻辑 }
常见踩坑提醒
- 不要用
Fling(0)替代StopScroll():该方法仅会向Scroller传递一次0速度的滚动请求,Scroller仍会基于当前已有的惯性速度持续计算滚动偏移,无法立刻终止原有滚动流程。 - 不要在Animator的数值更新回调中手动调用
ScrollTo():这种写法会和Scroller的滚动任务产生执行竞态,反而会加剧动画残留问题。正确做法是让ObjectAnimator直接绑定scrollY属性,由系统统一驱动属性值变化。 - 切换到使用
SmoothScrollTo()实现滚动时,同样需要在调用前执行StopScroll(),否则依然会出现原有fling动画残留的问题。
内容的提问来源于stack exchange,提问作者AlexTsy
相关产品推荐
相关产品推荐

