如何在Scrollview停止滚动前提前预知其最终停留位置
ScrollView 提前获取最终停留位置方案
完全可以在滚动停止前拿到精准的最终停留偏移,你之前靠标记减速起点手动测算距离不准是必然的——系统滚动减速是非线性曲线,还会自动处理边界回弹、分页/Item吸附逻辑,手动套运动公式算出来的结果和实际落点偏差会非常大,根本没必要自己算。
实现逻辑
ScrollView 从用户拖拽状态切换到自由减速状态的瞬间,系统内部已经算好了最终停留的位置,直接读取系统存储的计算结果就行,不需要自己做运动学推导:
- 监听ScrollView的滚动状态,区分「用户拖拽中」「自由减速中」「滚动停止」三个状态
- 进入减速状态的瞬间,直接读取系统计算好的目标偏移量,这个值和滚动完全停止后的实际偏移误差小于1px
- 如果减速过程中用户再次触碰ScrollView打断滚动,清空之前的预判结果,等再次进入减速状态时重新读取即可
各端核心实现代码
Android 端
不管是ScrollView、NestedScrollView还是RecyclerView,减速逻辑都是靠内部的OverScroller实现的,直接拿OverScroller里存的最终坐标即可:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(rv: RecyclerView, newState: Int) { if (newState == RecyclerView.SCROLL_STATE_SETTLING) { // 进入减速阶段,直接取scroller预存的最终落点 val flingerField = RecyclerView::class.java.getDeclaredField("mViewFlinger") flingerField.isAccessible = true val flinger = flingerField.get(rv) val scrollerField = flinger.javaClass.getDeclaredField("mOverScroller") scrollerField.isAccessible = true val overScroller = scrollerField.get(flinger) as OverScroller val finalY = overScroller.finalY val finalX = overScroller.finalX // 这里的finalX/finalY就是最终停留位置,直接提前修改对应位置的特效参数即可 } } })
原生ScrollView同理,自定义View重写
fling方法后可以直接读取内部OverScroller的finalX/finalY,不需要反射,稳定性更高。
iOS 端
在UIScrollView的减速开始代理回调里,直接读取系统计算的目标偏移:
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { CGPoint velocity = [scrollView.panGestureRecognizer velocityInView:scrollView]; // 直接调用系统方法获取计算好的最终停留偏移 CGPoint targetOffset = [scrollView _targetContentOffsetForProposedContentOffset:scrollView.contentOffset withScrollingVelocity:velocity]; // targetOffset就是最终停留位置,可直接用于特效参数预调整 }
如果是iOS10+,也可以直接通过scrollView.panGestureRecognizer的velocity配合decelerationRate计算投影偏移,结果和系统实际落点完全一致。
避坑提示
- 不要自己用匀减速公式算落点:系统默认减速是衰减率为0.998的非线性动画,碰到边界回弹、Item吸附逻辑时还会二次修正落点,手动计算的结果完全不可用
- 记得处理滚动打断场景:减速过程中如果用户再次触碰屏幕,之前拿到的目标偏移会失效,需要在状态切回拖拽时清空预判结果,等再次进入减速时重新取值
- 如果做了自定义吸附逻辑,要等吸附逻辑计算完最终位置后再取值,避免拿到未修正的偏移量
内容的提问来源于stack exchange,提问作者liangdapang
相关产品推荐
相关产品推荐

