1. 程式人生 > >使用Scroller實現彈性滑動

使用Scroller實現彈性滑動

scrollTo、scrollBy

View內部為了實現滑動提供了這兩個方法,但是使用這兩個方法滑動的效果是瞬間的不夠平滑,如何實現View的彈性滑動呢?這正是本博文討論的主題。另外這兩個函式滑動的是View的內容不是View本身。比如對於普通View好比TextView其內容就是文字,ImageView的內容則是drawable物件,採用這兩種方法滑動的時候其實分別滑動的是文字及drawable物件,對於ViewGroup採用這兩種方法滑動的時候則是對其子元素的滑動。所以想要使用scrollTo、scrollBy方法實現拖動View(指的是普通的View不包含ViewGroup)的效果必須在View外面在包一層ViewGroup。

Scroller類

上面提到使用scrollTo、scrollBy來滑動View的時候是很生硬得滑過去的,不夠平滑,自然使用者體驗也不好,因此我們要實現一個彈性的滑動。如何實現彈性滑動呢?方法有很多,但思想都是一致的,即將實現一段距離的滑動分成多次來進行,每一次滑動一小段,漸近式的滑動。本文只是介紹其中的一種即使用Scroller實現彈性滑動。以下結合例項看看Scroller是如何實現平滑滑動的呢 ?
public class SmoothScrollView extends LinearLayout{

	Scroller mScroller ;
	int startX;
	int startY;
public SmoothScrollView(Context context, AttributeSet attrs) {
	super(context, attrs);
	//建立Scroller例項
	mScroller = new Scroller(context);
	}


public void smoothScroll(int dx,int dy,int duration){
	//獲取滑動起點座標
	startX = getScrollX();
	startY = getScrollY();
	//設定滑動引數
	mScroller.startScroll(startX,startY,dx,dy,duration);
	//重新繪製View
	invalidate();
	}

@Override
public void computeScroll() {
	// TODO Auto-generated method stub
	super.computeScroll();
	boolean flag = mScroller.computeScrollOffset();
	//遞迴終止條件:滑動結束
	if(flag == false){
		return;
	}else{
	//mScroller.getCurrX(),mScroller.getCurrY()記錄的是此刻要滑動達到的目標座標
	scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
	}
	//遞迴呼叫
	invalidate();//或者postInvalidate()
	}
}
	
看到第9行,首先在SmoothScrollView 內部建立一個Scroller物件,第13行的smoothScroll方法是實現SmoothScrollView的平滑滑動,可以看到實現平滑滑動首先呼叫第18行Scroller的startScroll方法來設定滑動引數,下文會分析這個方法,這裡先放一放。然後在第20行呼叫invalidate方法,這個方法會導致SmoothScrollView重繪,從而呼叫draw方法之後又會呼叫computeScroll方法,在第24行可以看到這裡重寫了computeScroll方法,因此呼叫invalidate方法最終會導致computeScroll方法被呼叫。第27~29行呼叫Scroller的computeScrollOffeset方法並判斷是否滑動結束,computeScrollOffset是如何判斷滑動結束的呢?這裡也先放一放下文在分析。如果滑動未結束,執行第33行呼叫scrollTo滑動SmoothScrollView至此刻目的座標,然後遞迴呼叫invalidate方法。


以下是對Scroller幾個方法的分析:

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;
	//mDuration表示的是整個滑動持續的時間
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
從以上程式碼可以看到startScroll方法其實根本沒有滑動View只是對滑動引數進行設定。往下再來看看computeScrollOffset方法,computeScrollOffset返回true則表示滑動還沒結束返回false表示滑動結束,它的實現如下:
 public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
      //判斷此刻是否在有效滾動週期內
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
		//當前時刻滑動偏移量所佔份額
                float x = (float)timePassed * mDurationReciprocal;
    
                if (mInterpolator == null)
                    x = viscousFluid(x); 
                else
                    x = mInterpolator.getInterpolation(x);
                //獲取當前時刻要滾動到的位置
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                float timePassedSeconds = timePassed / 1000.0f;
                float distance = (mVelocity * timePassedSeconds)
                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
                
                mCurrX = mStartX + Math.round(distance * mCoeffX);
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distance * mCoeffY);
                // 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;
    }
    
看到這我們應該明白 SmoothScrollView 是如何實現讓自己平滑滑動的呢?實際上正真讓SmoothScrollView 產生平滑滑動的並非是Scroller而是 SmoothScrollView 自己,是 SmoothScrollView 自己多次呼叫了自己的s crollerTo 方法並且每次滑動一小步從而實現平滑滑動,而 Scroller 類乾的事只是輔助 SmoothScrollView 計算每一次小滑動要到達的目標座標,而實現多次呼叫了自己的scrollerTo方法這裡沒有使用迴圈體則是利用程式設計技巧“遞迴呼叫”invalidate方法達到多次呼叫scrollTo方法的目的從而實現平滑滑動。