问题描述
在开发时我们可能经常需要使用到ViewPager的onPageScrolled(int position, float offset, int offsetPixels)方法来获取ViewPager的滚动信息,然而在使用中发现onPageScrolled并不能准确地回调每一个滚动信息,可能会错过一些信息,在一些手机上甚至连onPageScrolled滚动停止(offset参数为0的状态)的信息也不会回调。
问题分析
通过阅读ViewPager的源码知道,ViewPager的滚动是配合Scroller实现的,而Scroller的实现原理就是在Scroller的动画时间内不断的调整View的位置然后重绘布局,而onPageScrolled的回调和view位置的调整是同步的,但是View的重新绘制是耗时的,这也就是说,onPageScrolled的回调频率和view的重新绘制相关联,如果手机的性能差一点,View的刷新次数少,那么onPageScrolled的回调次数也会相对减少。
后来又发现onPageScrolled的最终状态的回调在部分手机上存在问题,有部手机很卡也能正常回调,有部手机比较流畅却也没有回调,所以可能和安卓系统版本有关,于是对比了一下ViewPager在各个版本的实现,发现了问题;我们知道要通过Scroller实现滚动需要实现computeScroll方法,在这个方法里判断滚动是否已完成,没有完成则通过scrollTo方法滚动到相应的位置。下面是ViewPager的实现:
@Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
// Done with scroll, clean up state.
completeScroll(true);
}
可以看到如果mScroller没有Finished则会通过pageScrolled回调onPageScrolled 并滚动到指定位置,如果已经Finished则调用completeScroll方法完成最终的状态,我们的问题是onPageScrolled在最终状态的调用在有些手机上不正常,所以这其中的玄机很可能发生在completeScroll方法中,于是进一步查看completeScroll方法:
private void completeScroll(boolean postEvents) {
boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
if (needPopulate) {
// Done with scroll, no longer want to cache view drawing.
setScrollingCacheEnabled(false);
mScroller.abortAnimation();
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (x != oldX) {
pageScrolled(x);
}
}
}
.....省略部分代码
}
这里主要注意下面这个判断,通过查阅各个版本的源码,发现只有在api23以上才有这个判断
if (x != oldX) {
pageScrolled(x);
}
而这个判断就是回调onPageScrolled方法的。所以答案就出来了:假如不是api23的手机,如果scroller已经finish状态(动画的时间到),而view还没来得及完全刷新,就会走到completeScroll方法,而这时api23以下的手机不回回调onPageScrolled方法。
问题总结
ViewPager的onPageScrolled方法比较不可靠,如果有一些比较关键的代码要放在这里处理最好改用其它方式实现,不然稳定性不高,容易出问题。