Scroller的原理与简单使用

作者: 1409d11db72f | 来源:发表于2016-08-19 15:20 被阅读248次

    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()获得。

    mScrollXY.png

    如上图,我们得到以下结论:
    (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()方法只是一个空实现,因此我们要完成滑动效果需要复写此方法。如下图:

    QQ截图20160819145459.png

    在方法中,我们利用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的源码讲解也有很多,我也是借鉴别人的列子通过自己的理解完成的本文,因此不能完全说的上是原创。也希望大家能够理解。

    献上代码
    ScrollDemo
    小结,通过Scroller我们可以实现很多弹性滑动的效果。

    相关文章

      网友评论

      本文标题:Scroller的原理与简单使用

      本文链接:https://www.haomeiwen.com/subject/lbgpsttx.html