相關(guān)文章
Android View體系(一)視圖坐標(biāo)系
Android View體系(二)實(shí)現(xiàn)View滑動的六種方法
Android View體系(三)屬性動畫
前言
在Android View體系(二)實(shí)現(xiàn)View滑動的六種方法這篇文章中我們講到了用Scroller來實(shí)現(xiàn)View的滑動逸邦,所以這篇文章我們就不介紹Scroller是如何使用的了衷旅,本篇就從源碼來分析下Scroller為何能夠?qū)崿F(xiàn)View的滑動障涯。
1.Scroller的構(gòu)造函數(shù)
要想使用Scroller,必須先調(diào)用new Scroller()乔宿,我們先來看看Scroller的構(gòu)造函數(shù):
/**
* 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有三個構(gòu)造函數(shù)位迂,通常情況我們都用第一種,第二種需要傳進(jìn)去一個差值器Interpolator 详瑞,如果不傳則采用默認(rèn)的差值器(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()方法中并沒有調(diào)用類似開啟滑動的方法,而是保存了傳進(jìn)來的各種參數(shù):startX和startY表示滑動開始的起點(diǎn)蛤虐,dx和dy表示滑動的距離,duration則表示滑動持續(xù)的時間肝陪。所以startScroll()方法只是用來做前期準(zhǔn)備的并不能使View進(jìn)行滑動驳庭。關(guān)鍵是我們在startScroll()方法后調(diào)用了 invalidate()方法,這個方法會導(dǎo)致View的重繪,而View的重繪會調(diào)用View的draw()方法饲常,draw()方法又會調(diào)用View的computeScroll()方法蹲堂,我們重寫computeScroll()方法:
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//通過不斷的重繪不斷的調(diào)用computeScroll方法
invalidate();
}
}
我們在computeScroll()方法中通過Scroller來獲取當(dāng)前的ScrollX和ScrollY然后調(diào)用scrollTo()方法來進(jìn)行View的滑動,接著調(diào)用invalidate方法來讓View進(jìn)行重繪贝淤,重繪就會調(diào)用computeScroll()方法來實(shí)現(xiàn)View的滑動柒竞。這樣我們就通過不斷的移動一個小的距離并連貫起來就實(shí)現(xiàn)了平滑移動的效果。但是在Scroller中我們?nèi)绾文塬@取當(dāng)前的位置的ScrollX和ScrollY呢播聪?我們忘了一點(diǎn)就是在調(diào)用scrollTo()方法前會調(diào)用Scroller的computeScrollOffset()方法朽基,接下來我們就來看看computeScrollOffset()方法。
3.Scroller的computeScrollOffset方法
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
//動畫持續(xù)的時間
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
//根據(jù)插值器來算出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;
}
首先會計算動畫持續(xù)的時間timePassed离陶,如果動畫持續(xù)時間小于我們設(shè)置的滑動持續(xù)時間mDuration稼虎,則執(zhí)行Swich語句,因?yàn)樵趕tartScroll()方法中mMode為SCROLL_MODE所以執(zhí)行分支語句SCROLL_MODE招刨,然后根據(jù)插值器Interpolator來計算出在該時間段里面移動的距離霎俩,賦值給mCurrX和mCurrY,這樣我們就能通過Scroller來獲取當(dāng)前的ScrollX和ScrollY了沉眶。另外打却,computeScrollOffset()的返回值如果為true則表示滑動未結(jié)束,false則表示滑動結(jié)束谎倔,所以如果滑動未結(jié)束我們就得持續(xù)的調(diào)用scrollTo()方法和invalidate()方法來進(jìn)行View的滑動柳击。