Android View體系(四)從源碼解析Scroller
在Android View體系(二)實現View滑動的六種方法這篇文章中我們講到了用Scroller來實現View的滑動,所以這篇文章我們就不介紹Scroller是如何使用的了,本篇就從源碼來分析下Scroller為何能夠實現View的滑動。
1.Scroller的構造函數
要想使用Scroller,必須先調用new Scroller(),我們先來看看Scroller的構造函數:
/** * Create a Scroller with the default duration and interpolator. */ public Scroller(Context context) { this(context, null); } /** * Create a Scroller with the specified interpolator. If the interpolator is * null, the default (viscous) interpolator will be used. "Flywheel" behavior will * be in effect for apps targeting Honeycomb or newer. */ public Scroller(Context context, Interpolator interpolator) { this(context, interpolator, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB); } /** * Create a Scroller with the specified interpolator. If the interpolator is * null, the default (viscous) interpolator will be used. Specify whether or * not to support progressive "flywheel" behavior in flinging. */ public Scroller(Context context, Interpolator interpolator, boolean flywheel) { mFinished = true; if (interpolator == null) { mInterpolator = new ViscousFluidInterpolator(); } else { mInterpolator = interpolator; } mPpi = context.getResources().getDisplayMetrics().density * 160.0f; mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); mFlywheel = flywheel; mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning }
Scroller有三個構造函數,通常情況我們都用第一種,第二種需要傳進去一個差值器Interpolator ,如果不傳則采用默認的差值器(viscous)。
2.Scroller的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; }
在startScroll()方法中并沒有調用類似開啟滑動的方法,而是保存了傳進來的各種參數:startX和startY表示滑動開始的起點,dx和dy表示滑動的距離,duration則表示滑動持續的時間。所以startScroll()方法只是用來做前期準備的并不能使View進行滑動。關鍵是我們在startScroll()方法后調用了 invalidate()方法,這個方法會導致View的重繪,而View的重繪會調用View的draw()方法,draw()方法又會調用View的computeScroll()方法,我們重寫computeScroll()方法:
@Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); //通過不斷的重繪不斷的調用computeScroll方法 invalidate(); } }
我們在computeScroll()方法中通過Scroller來獲取當前的ScrollX和ScrollY然后調用scrollTo()方法來進行View的滑動,接著調用invalidate方法來讓View進行重繪,重繪就會調用computeScroll()方法來實現View的滑動。這樣我們就通過不斷的移動一個小的距離并連貫起來就實現了平滑移動的效果。但是在Scroller中我們如何能獲取當前的位置的ScrollX和ScrollY呢?我們忘了一點就是在調用scrollTo()方法前會調用Scroller的computeScrollOffset()方法,接下來我們就來看看computeScrollOffset()方法。
3.Scroller的computeScrollOffset方法
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); //根據插值器來算出timePassed這段時間移動的距離 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; }
首先會計算動畫持續的時間timePassed,如果動畫持續時間小于我們設置的滑動持續時間mDuration,則執行Swich語句,因為在startScroll()方法中mMode為SCROLL_MODE所以執行分支語句SCROLL_MODE,然后根據插值器Interpolator來計算出在該時間段里面移動的距離,賦值給mCurrX和mCurrY,這樣我們就能通過Scroller來獲取當前的ScrollX和ScrollY了。另外,computeScrollOffset()的返回值如果為true則表示滑動未結束,false則表示滑動結束,所以如果滑動未結束我們就得持續的調用scrollTo()方法和invalidate()方法來進行View的滑動。
浙公網安備 33010602011771號