|  | Scroller是Android中处理滑动效果的核心类。它通过配合View所提供的scrollTo()和scrollBy()这两个方法,就能够使用多种多样的滑动效果。网上也有很多文章介绍关于Scroller的使用,那么在这篇文章中,我尽可能缩减篇幅将Scroller的原理和实现介绍一下。 
 1. scrollTo()和scrollBy()
 首先介绍一下scrollTo()和scrollBy()这两个方法:
 
 | /** * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to
 * @param y the y position to scroll to
 */
 public void scrollTo(int x, int y) {
 if (mScrollX != x || mScrollY != y) {
 int oldX = mScrollX;
 int oldY = mScrollY;
 mScrollX = x;
 mScrollY = y;
 invalidateParentCaches();
 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
 if (!awakenScrollBars()) {
 postInvalidateOnAnimation();
 }
 }
 }
 | 
 scrollTo()是让当前的View能够平移到所给定的位置,然后通过起始位置和结束位置的坐标,根据onScrolledChanged()方法的调用最终完成绘制。
 
 
 
 | /** * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
 public void scrollBy(int x, int y) {
 scrollTo(mScrollX + x, mScrollY + y);
 }
 | 
 而scrollBy()方法的实现是根据scrollTo()而完成的,唯一的区别在于scrollBy()是根据当前已经位移的坐标为基础再继续进行位移。也就是说scrollTo()实现的是输入参数的绝对滑动,而scrollBy()实现的是输入参数的相对滑动。
 
 另外,scrollTo()和scrollBy()方法中都提到了mScrollX和mScrollY两个属性,这两个属性可以分别通过getScrollX()和getScrollY()获得。
 
 
   
 如上图,我们得到以下结论:
 (1)View的左边界位于View内容左边界的左侧时,mScrollX < 0;反之,mScrollX > 0。
 (2)View的上边界超过View内容上边界时,mScrollY < 0;反之,mScrollY > 0。
 
 2. Scroller的滑动偏移计算
 通过阅读Scroller的源码我们发现,Scroller虽然作为View滑动的重要依据,但是却不能真正操纵View滑动,他是通过旧的坐标值和新的坐标值算出偏移距离,配合View的computeScroll()方法完成滑动操作。
 
 
 
 | /** * Start scrolling by providing a starting point, the distance to travel,
 * and the duration of the scroll.
 *
 * @param startX Starting horizontal scroll offset in pixels. Positive
 *        numbers will scroll the content to the left.
 * @param startY Starting vertical scroll offset in pixels. Positive numbers
 *        will scroll the content up.
 * @param dx Horizontal distance to travel. Positive numbers will scroll the
 *        content to the left.
 * @param dy Vertical distance to travel. Positive numbers will scroll the
 *        content up.
 * @param duration Duration of the scroll in milliseconds.
 */
 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
 mMode = SCROLL_MODE;
 mFinished = false;
 mDuration = duration;
 mStartTime = AnimationUtils.currentAnimationTimeMillis();
 mStartX = startX;
 mStartY = startY;
 mFinalX = startX + dx;
 mFinalY = startY + dy;
 mDeltaX = dx;
 mDeltaY = dy;
 mDurationReciprocal = 1.0f / (float) mDuration;
 }
 | 
 startX - 起始滑动的X坐标
 startY - 起始滑动的Y坐标
 dx - X方向上的滑动偏移距离
 dy - Y方向上的滑动偏移距离
 duration - 完成滑动的时间
 
 通过代码我们可以看出,Scroller的startScroll()方法作为View的滑动依据只是单纯的对于变量进行和一系列的赋值操作。
 
 3. 实现滑动效果
 通过上面的startScroll()方法的分析,我们了解到该方法只是针对View滑动偏移的计算和赋值工作,并没有真正的执行滑动效果。而真正实现滑动的是View类的computeScroll()方法完成的。
 
 在源码中,我们可以看到View类的computeScroll()方法只是一个空实现,因此我们要完成滑动效果需要复写此方法。如下图:
 
   
 在方法中,我们利用Scroller的computeScrollOffset()方法去判断是否View有滑动事件的发生,如果有,即可通过scrollTo()方法完成滑动偏移。
 
 
 
 
 
 | /** * Call this when you want to know the new location.  If it returns true,
 * the animation is not yet finished.
 */
 public boolean computeScrollOffset() {
 if (mFinished) {
 return false;
 }
 
 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
 
 if (timePassed < mDuration) {
 switch (mMode) {
 case SCROLL_MODE:
 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
 mCurrX = mStartX + Math.round(x * mDeltaX);
 mCurrY = mStartY + Math.round(x * mDeltaY);
 break;
 case FLING_MODE:
 final float t = (float) timePassed / mDuration;
 final int index = (int) (NB_SAMPLES * t);
 float distanceCoef = 1.f;
 float velocityCoef = 0.f;
 if (index < NB_SAMPLES) {
 final float t_inf = (float) index / NB_SAMPLES;
 final float t_sup = (float) (index + 1) / NB_SAMPLES;
 final float d_inf = SPLINE_POSITION[index];
 final float d_sup = SPLINE_POSITION[index + 1];
 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
 distanceCoef = d_inf + (t - t_inf) * velocityCoef;
 }
 
 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
 
 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
 // Pin to mMinX <= mCurrX <= mMaxX
 mCurrX = Math.min(mCurrX, mMaxX);
 mCurrX = Math.max(mCurrX, mMinX);
 
 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
 // Pin to mMinY <= mCurrY <= mMaxY
 mCurrY = Math.min(mCurrY, mMaxY);
 mCurrY = Math.max(mCurrY, mMinY);
 
 if (mCurrX == mFinalX && mCurrY == mFinalY) {
 mFinished = true;
 }
 
 break;
 }
 }
 else {
 mCurrX = mFinalX;
 mCurrY = mFinalY;
 mFinished = true;
 }
 return true;
 }
 | 
 4. invalidate()
 computeScroll()方法并非凭空实现的,它需要调用invalidate()去进行View的重新绘制,invalidate()的源码中有对于computeScroll()方法的调用。
 
 网上关于Scroller的源码讲解也有很多,我也是借鉴别人的列子通过自己的理解完成的本文,因此不能完全说的上是原创。也希望大家能够理解。
 
 
 小结,通过Scroller我们可以实现很多弹性滑动的效果。
 -------------------------------------------------------
 作者:NeroIsNovice
 原文链接:https://www.jianshu.com/p/3cc780dc26b5
 
 
 
 
 
 
 程序猿的技术大观园:www.javathinker.net
 
 
 程序猿的技术大观园:www.javathinker.net
 |  |