Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android平滑移動——Scroller類研究

Android平滑移動——Scroller類研究

編輯:關於Android編程

 

Scroller是Android中View平滑移動的一個輔助類,對於剛接觸Scroller的人群來說它可能難以理解:

1、它是怎樣滑動View的(如何與View關聯的)?
2、又是誰觸發了它?

其實要分析這兩個問題,主要還得從View的繪制流程開始分析:
關於View的繪制流程,網上資料眾多,基本上相差無幾,這裡就不再闡述,下面提取下解析Scroller功能的必要的幾個View的繪制方法:

scrllTo()/scrollBy() ---> invalidate()/postInvalidate() ---> computeScroll();(這個流程我們可以分析源碼得到)。scrllTo()/scrollBy()是view移動的兩個方法;它會更新View的新的坐標點,然後調用invalidate/postInvalidate方法刷新view; 滑動完成後再調用computeScroll()方法;computeScroll()是View.java的一個空的方法,需要由我們去實現處理。

根據上面的流程我們想想,當我們在computeScroll()方法中調用scrollTo/scrollBy的時候,會發生什麼?

答:很簡單,這樣就會達成一個死循環,在同一個位置不斷的刷新View,只是由於系統緩存與一些優化機制,我們看不出來它在刷新而已!

再當我們在computeScroll()方法中調用scrollTo/scrollBy的時候,同時不斷的改變Y坐標的值又會發生什麼?

答:跟上面一樣,會死循環刷新View,只是由於Y坐標不斷的在變化,導致了View根據Y坐標變化規律上下移動,這樣一來,如果Y坐標的變化是有規律的,是慢慢向下移動的,那這就達到了我們今天要研究的效果----平滑移動了;而這裡我們今天要談的Scroller就是這樣一個工具類,給我們提供有規律變化的坐標的工具類;嘿嘿,似乎發現了什麼....

有種撥雲見日的感覺啊,原來平滑移動如此easy,那麼與其說Scroller是Android中View平滑移動的一個輔助類,不如直接說Scroller是一個計算坐標的工具類。其實Scroller與View的滑動是沒有關系的,它只是計算在動畫執行某個時間所在的某個位置的坐標,這樣就形成了坐標路線,再view根據坐標路線循環invalidate在界上新顯示,就形成了我們看到的平滑移動了。

知道了Scroller的工作原理,下面根據源碼分析一下Scroller類常用的幾個方法:
1、startScroll()方法:
源碼:
    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;
    }

簡單到讓人無法相信,僅僅是設置了一下動畫開始時間,起始坐標,終點坐標、時間倒數(僅僅是方便計算而已)等變量而已;其實這樣我們的動畫可以說已經開始了,只是沒有根據坐標繪制到界面上而已;因為它這裡保存了開始時間,當平滑開始的時候,Scroller就可以根據滑動的時間差來計算當前坐標應該處的位置,View根據坐標invalidate就可以滑動了;

當然這裡影響到坐標計算的還有一個就是加速器,在重載的構造方法方法 public Scroller(Context context, Interpolator interpolator) 裡面有詳述:這裡我們也可以自定義自己的加速器,具體原理與如何自定義這裡就不闡述了;


2、computeScrollOffset()方法:
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                float x = 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:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                final float t_inf = (float) index / NB_SAMPLES;
                final float t_sup = (float) (index + 1) / NB_SAMPLES;
                final float d_inf = SPLINE[index];
                final float d_sup = SPLINE[index + 1];
                final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
                
                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;
    }

看代碼主要當平滑沒有完成的情況下,計算出當前滑動的坐標位置,如果平滑完成了,就不需要計算了;這裡有一個SCROLL_MODE和FLING_MODEL兩種滑動模式,這裡是SCROLL_MODE手動拖動平滑模式,FLING_MODEL是由於手指滑動速率來判斷慣性滑動。一般這個方法執行完成之後,根據返回值判斷是否需要invalidate/postinvalidate,再根據Scroller計算好的坐標值,View將scrollTo/scrollBy到新的坐標位置;

3、fling()方法:

 

    public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
        // Continue a scroll or fling in progress
        if (mFlywheel && !mFinished) {
            float oldVel = getCurrVelocity();

            float dx = (float) (mFinalX - mStartX);
            float dy = (float) (mFinalY - mStartY);
            float hyp = FloatMath.sqrt(dx * dx + dy * dy);

            float ndx = dx / hyp;
            float ndy = dy / hyp;

            float oldVelocityX = ndx * oldVel;
            float oldVelocityY = ndy * oldVel;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }

        mMode = FLING_MODE;
        mFinished = false;

        float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
     
        mVelocity = velocity;
        final double l = Math.log(START_TENSION * velocity / ALPHA);
        mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;

        float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
        float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;

        int totalDistance =
                (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
        
        mMinX = minX;
        mMaxX = maxX;
        mMinY = minY;
        mMaxY = maxY;

        mFinalX = startX + Math.round(totalDistance * coeffX);
        // Pin to mMinX <= mFinalX <= mMaxX
        mFinalX = Math.min(mFinalX, mMaxX);
        mFinalX = Math.max(mFinalX, mMinX);
        
        mFinalY = startY + Math.round(totalDistance * coeffY);
        // Pin to mMinY <= mFinalY <= mMaxY
        mFinalY = Math.min(mFinalY, mMaxY);
        mFinalY = Math.max(mFinalY, mMinY);
    }

 

滑動,這個滑就跟GestureDetector.OnGestureListener這個接口中的onFling事件一樣,根據滑動速率來判斷一些事件。主要處理一些一些慣性坐標變化(慣性行為)。用得比較少;

 

4、abortAnimation()方法:

 

    public void abortAnimation() {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }

 

看源碼,停止動畫(就是設置一個標記而已,不再計算坐標的變化值)

在代碼的開始外看到這樣一段靜態代碼塊:
    static {
        float x_min = 0.0f;
        for (int i = 0; i <= NB_SAMPLES; i++) {
            final float t = (float) i / NB_SAMPLES;
            float x_max = 1.0f;
            float x, tx, coef;
            while (true) {
                x = x_min + (x_max - x_min) / 2.0f;
                coef = 3.0f * x * (1.0f - x);
                tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
                if (Math.abs(tx - t) < 1E-5) break;
                if (tx > t) x_max = x;
                else x_min = x;
            }
            final float d = coef + x * x * x;
            SPLINE[i] = d;
        }
        SPLINE[NB_SAMPLES] = 1.0f;

        // This controls the viscous fluid effect (how much of it)
        sViscousFluidScale = 8.0f;
        // must be set to 1.0 (used in viscousFluid())
        sViscousFluidNormalize = 1.0f;
        sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
    }

這一段代碼讓我糾結了好久,照我的理解應該是把慣性滑動模式中的滑動距離比率(不好描述)分成100份,精確到0.00001,在慣性滑動的時候根據當前時間比率來計算當前坐標處於的位置;

其實說到這裡,感覺Android裡面的動畫,其實就是對於坐標的精確計算,這裡只是簡單的平滑滑動,有能力者我們也可以根據自己的需求寫出自己的坐標計算類來達到我們的需求;



Scroller的使用方法這裡不闡述了,這裡引用一下另一們童鞋的博文:http://ipjmc.iteye.com/blog/1615828。這裡有一個很好的demo,可以參考學習;歡迎留言討論。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved