View滑动的原理,解析scrollTo,ScrollBy和Scroller

滑动的概念,很不好理解,想通过这一篇博文,让自己和不明白的人加深下理解,希望我的语言能够使你明白:

首先我们要理解,为什么使用滑动?
因为手机屏幕,给我们的视图就那么大,我们看到的视图有限,要想在固定的视图,去展示更多的东西,我们就需要去滑动,把隐藏的那部分变为可视视图。我们可以把能展示的大小看成一个很大的画板,我们展示看到的区域是里面的一部分,当我们需要看到更多的时候,就需要去滑动画板,让自己看到那些被隐藏的。

然后,还有一个很重要的概念,就是我们滑动的是View内容,而并不是view本身,一个特别好理解的例子就是listview,我们把listview设置宽和高都沾满屏幕,当我们上下滑动时,我们滑动的是listview里面内容,而不是listview本身,listview还是铺满整个屏幕当我们滑动的时候,所以当我们想一个button发生位置改变时,要调用的不是button.scroolTo(),或者button.scroolBy(),而是应该调用它的父控件,layout.scroolTo()或者layout.scrooBy()。此时,button就是layout中的内容。当调用button的scroolTo或者button.scroolBy时,button里面的内容会被移动看不见,但button还在最初的位置。

区分scroolTo和scroolBy的区别:

看scroolTo的源码:

 /** * 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();
            }
        }
    }

参数x,y是想要滚动的偏移x,和y值,mScroll、mScrollY表示的是离视图起始位置的偏移量,意思就是当你滚动了,这个才有值,如果没有发生滚动,这两个值就是0。
scrollTo的滚动是相对于这个view的起始位置,只执行一次,因为起始位置就一个,所以,当执行一次后,在取调用这个view的scrollTo就不在起作用了,第一次调用后,oldX,oldY会被赋值成想x,y的值,当第二次调用时,x,y值不发生变化时,因为mScrollX的值就是x的值,mScrollY的值就是y的值,所以,在if(mScrollX !=x || mScrollY !=Y)这句下面不会被执行,那么onScrollChanged()方法就不会被执行,因此,当第二次调用时,如果x,y值不变,那么就不会发生滚动。

看scrollBy的源码:

  /** * 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这个方法,但里面的x,y值每次调用都会发生改变,参数x,y同样是想要滚动的偏移量,但mScrollX+x,mScrollY+y就相当于在上一次基础上,因为现在的mScrollX,mScrollY的值就是上一次滚动后的view的x,y的偏移量。

在举个例子,我要让你和我下次看到后不会不明白为之:
mScrollX,mScrolly不滚动时,值为0.
当我们调用scrollTo(20,20)时,这时,mScrollX的值就是20,mScrollY的值也是20,当我们再次调用scrollBy(20,20)时,我们的mScrollX的值就是40,mScrollY的值也是40。
但当我们再一次执行,strollTo(20,20)时,mScrollX的值就是20,mScrollY的值也是20,而执行同样的scrollBy(20,20)时,在mScrollX,和mScrollY等于40的情况下,这时mScrollX就是60,mScrollY也是60。简而言之,mScrollTo执行后,mScrollX就是x的值,mScrollY值就是y的值,而scrollBy执行后,是在原来的基础上加上x,y值。注意一点,这里的正值是水平方向是想做,竖直方向是像上,向右和向下则是负值。够明白了吧,我对我自己说…..

scrollTo和scrollBy都是瞬时滑动的,当我们需要弹性滑动,有个滑动过程时,就需要用到Scroller。
一般我们代码中这么写:

 Scroller mScroller=new Scroller(mContext);
    private void smoothScrollTo(int destX,int destY){
        int scrollX=getScrollX();
        int deltaX=destX-scrollX;
        mScroller.startScroll(scrollX,0,deltaX,0,2000);
        invalidate();
    }
    @Override
    public void computeScroll() {
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }

参数destX,destY表示要滚动的偏移量, startScroll方法中的的5个参数的意思是:

 mScroller.startScroll(int startX,int startY,int dx,int dy,int duration);

startX:起始的x方向偏移量,或者说目前的x方向偏移量
startY:起始的y方向偏移量,或者说目前的y方向偏移量
dx:x方向要滚动的偏移量
dy:y方向要滚动的偏移量
duration:多长时间完成这个过程 单位ms

我们打开startScroll这个方法,代码如下:

/** * 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;
    }

从这里我们可以看到,调用startScroll方法是无法让view滑动的,方法里只是去给一些变量去赋值,里面并没有滑动的相关操作,那么view怎么去滑动呢,答案是在它下面的invalidate这个方法。invalidate这个方法会将view重新绘制,在重新绘制的过程中,我们会调用View的draw方法,在draw方法里面我们会调用dispatchDraw()这个方法,在dispatchDraw()方法里面我们会调用drawChild()方法,在这个方法里就有我们要用的computerScroll()这个方法,而computerScroll方法在View中是一个空的方法,需要我们自己去实现,也正是因为有了computerScroll方法,我们的view才能去滚动。
整个过程是这样的:当View重绘后,会调用draw方法里的computeScroll方法,而computeScroll又会去向Scroller获取当前的scrollX,scrollY的值,然后通过scrollTo方法去实现滑动,接着有在此调用postInvalidate方法进行第二次重绘,这次有会在draw方法中调用computeScroll方法,,再一次通过Scroller去获得scrollX,scrollY的值并通过scrollTo去滑动,如此反复,直到在固定的时间内滑动完成。

看一下scrollX、scrollY值是怎样改变的,computeScrollOffset源码如下:

 /** * 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;
    }

方法的大意是通过时间流逝的百分比去计算偏移量的改变的百分比,从而计算出当前偏移量的值,最后的返回值true代表整个滑动过程位结束,false代表滑动结束。可见Scroller本身并不能让View去滑动,一定要配合computeScroll整个方法才可以,经过许多次重绘,每次重绘又去调用scrollTo这个滚动方法,去一点点改变scrollX、scrollY的值,去实现滑动,在规定的时间内,每一次微小的滑动过程累加,就是弹性滑动的整个过程。

参考:
Scroll原理-附ScrollView源码分析

任玉刚老师android开发艺术探索中的View滑动

你可能感兴趣的:(滑动)