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,可以參考學習;歡迎留言討論。