最近在修改fragmentation 的bug時,其中SwipeBackLayout 的實現使用是ViewDragHelper ,而ViewDragHelper 使用的是OverScroller,OverScroller 在大部分時候是可以取代Scroller的。所以先從Scroller來分析,這些知識用到好幾次,經常忘記,也算做個筆記。
Android開發中,但在這些api的實際使用過程中,開發人員很容易在移動方向、移動距離上產生迷惑,本文通過圖例總結了這四種方法的區別和聯繫。
很多文章中介紹Scroller的彈性滑動的時候,經常把view的方法和Scroller類的方法,混在一起,爲了方便理解,下面將分開介紹View的scroll方法和Scroller類的方法
一、View的scroll相關方法
注意下面這些方法,是View的方法
在自定義控件的時候,如果涉及到滑動事件,經常需要使用View提供的諸如scrollTo()、scrollBy()、getScrollX()、getScrollY()等方法。
滑動對象
scrollTo和scrollBy用於滑動View的內容,而不是改變View本身所處的位置。所以,單獨的View滑動很少見,更多的是ViewGroup調用scroll方法滑動子控件的位置。比如,使用TextView對象調用scrollTo或者ScrollBy方法,會發現TextView裏面的文本內容的位置發生改變,而TextView本身所處的位置沒有變化,也就是說Textview的點擊事件位置沒有變化。(View動畫,關於其位置的變化,可閱讀這篇文章)
/**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
*
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
return mScrollX;
}
/**
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
*
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
return mScrollY;
}
getScrollX()和getScrollY()
返回值mScrollX和mScrollY分別表示距離起始View位置的X軸或Y軸方向上的偏移量,而不是View在X軸或Y軸方向上的座標值,用於記錄偏移增量的兩個變量。所以,mScrollX和mScrollY的初始值爲0和0。
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
參數 x,y,表示要移動到的位置,賦值給mScrollX和mScrollY
比如,我想移動到(100,100)這個位置,那麼偏移量就是(0,0)-(100,100)=(-100,100),所以調用的時候就是view.scrollTo(-100, -100),這樣才能達到我們想要的偏移效果。
這個方法,已經能夠實現view的位置移動,看上去是一瞬間完成的,沒有動畫效果。
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
參數 x,y,表示相對於mScrollX和mScrollY要移動的偏移量,
public void scrollBy(int x, int y) {
// scrollBy實際是調用了scrollTo方法:scrollTo(mScrollX + x, mScrollY + y)
scrollTo(mScrollX + x, mScrollY + y);
}
關於移動的正負值和方向的關係,參考下面這張圖,圖是網上找的
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
computeScroll
View的computeScroll () 是個空方法,從註釋來看,這個方法是爲了配合Scroller ,來實現動畫移動,也就是彈性滑動,而誕生的一個方法。
這個方法會在View的四個地方被調用:
- draw() 一般都是因爲這個方法的調用,觸發了computeScroll ()方法
- updateDisplayListIfDirty()
- buildDrawingCacheImpl() 調用這個方法的方法,已經被廢棄
- createSnapshot()
在介紹彈性滑動前,先來了解一下Scroller的相關方法
二、Scroller
在實現彈性滑動中,只使用到兩個方法:
- startScroll
- computeScrollOffset
彈性滑動的實現
下面的代碼中,直接調用的函數,都是View的函數。調用smoothScroll() 就可以實現彈性滑動
Scroller mScroller = new Scroller(mContext);
//自定義的函數
private void smoothScroll(int destX, int destY) {
int scrollX = getScrollX();
int deltaX = destX - scrollX;
mScroller.startScroll(scrollX, 0, deltaX, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
下面來分析一下這兩個函數的源碼:
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代表整個滑動需要的時間。
這段代碼,並沒有設置view移動的代碼,那是如何實現彈性滑動的呢?
奧祕就是在startScroll() 後面緊接着調用了invalidate(),最終會調用 computeScroll()這個自定義函數,在computeScroll()函數中,調用scrollTo(),然後又調用了invalidate()。不斷的調用scrollTo() 來進行view的移動,來實現滑動動畫。
在computeScroll()中 調用computeScrollOffset()函數 ,來判斷要不要進行移動,以及下一次要移動到的位置
下面看一下,computeScrollOffset()函數的源碼,代碼很簡單,註釋寫了一些說明
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
* 如果返回True,表示動畫未完成。返回false,表示動畫完成
*/
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);
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;
}
至此,彈性滑動的原理就算是介紹完了