1、scrollTo/scrollBy
總所周知,使用 View 的 scrollTo 或者 scrollBy 方法實(shí)現(xiàn) View 內(nèi)容的滑動是一瞬間完成的,毫無過度可言弃舒,那么 Android 提供 Scroller 就可以解決這個問題,讓 View 內(nèi)容的滑動可以在一段時間內(nèi)完成状原。
mImageView.scrollTo(100,100);
上面的示例代碼表示 mImageView 的內(nèi)容向左上角移動 100px聋呢,上面說過了,這種方式是一瞬間完成的颠区,很堅硬削锰。
為了解決這個問題,我們引入的 Scroller 類毕莱,下面看看如何使用這個類實(shí)現(xiàn)彈性滑動喂窟。
2、Scroller 的實(shí)現(xiàn)原理
準(zhǔn)備工作:使用 Scroller#startScroll(...) 方法確定 View 內(nèi)容的位置和需要滑動的距離央串,注意這個方法只是對 Scroller 對象的中一些屬性進(jìn)行賦值(具體看下下面源碼部分)磨澡,并沒有產(chǎn)生任何的滑動操作。
-
準(zhǔn)備工作完成之后质和,該如何利用 Scroller 實(shí)現(xiàn)彈性滑動
通過調(diào)用 View#invalidate() 方法去觸發(fā) View 的重繪操作稳摄,因?yàn)?View#draw 方法中會回調(diào) computeScroll() 這個方法,而它是個空方法饲宿,我們只要在 View 中復(fù)寫這個方法厦酬,就可以在每次 View 重繪制時獲取當(dāng)前 View 內(nèi)容需要滑動的距離胆描,然后調(diào)用 scrollTo 進(jìn)行滑動。
-
現(xiàn)在知道是通過 View#invalidate() 方法可以觸發(fā) View 進(jìn)行重繪仗阅,那么如何在重繪的回調(diào)中取計算當(dāng)前 View 內(nèi)容需要滑動的距離呢昌讲?
因?yàn)?View#computeScroll 方法會在 View#invalidate() 中回調(diào),那么我們多次調(diào)用 invalidate 方法就可以多次回調(diào)了减噪,并且在每一次回調(diào)中取設(shè)置當(dāng)前 View 需要滑動到指定的位置短绸。
如何計算 View 需要滑動的距離呢? 在 Scroller#onComputeScrollOffset() 方法內(nèi)部會根據(jù)時間去計算當(dāng)前 View 內(nèi)容的 scrollX 和 scrollY 的值筹裕,因此在 View#computeScroll() 方法中去對調(diào)用 onComputeScrollOffset 進(jìn)行計算即可醋闭,然后獲取計算后的值,然后調(diào)用 scrollTo 進(jìn)行滑動即可朝卒。
3证逻、示例代碼
我們在第 2 點(diǎn)中說了很多,可能會有點(diǎn)迷糊抗斤,下面以實(shí)際代碼來演示可能會更加清晰一些囚企。下面是第 2 點(diǎn)實(shí)現(xiàn)原理的具體代碼實(shí)現(xiàn):
//定義Scroller對象
Scroller mScroller = new Scroller(mContext);
/**
* @des 滑動到某個位置
* @param dx 表示x方向需要滑動的大小
* @param dy 表示y方向需要滑動的大小
*/
private void smoothScrollBy(int dx, int dy) {
int scrollX = getScrollX();
int scrollY = getScrollY();
//這個方法是在初始化坐標(biāo)(scrollX,scrollY)水平方向滑動dx像素,豎直方向滑動dy個像素瑞眼,滑動時間為500ms洞拨。
mScroller.startScroll(scrollX, scrollY, dx, dy, 500);
//該方法標(biāo)記該 View 會進(jìn)行重繪,并且回調(diào) computeScroll() 方法负拟。
invalidate();
}
//重寫View的computeScroll()方法,該方法會在View繪制的時候調(diào)用
@Override
public void computeScroll() {
//計算當(dāng)前的 scrollX 和 scrollY 的值歹河,并且根據(jù)返回值判斷滑動是否結(jié)束掩浙。
if (mScroller.computeScrollOffset()) {
//滾動結(jié)束
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//計算后的 scrollX 和 scrollY 的值
postInvalidate();//每次滑動之后,都要重繪一遍
}
}
4秸歧、源碼分析
4.1厨姚、彈性滑動入口 startScroll(int startX, int startY, int dx, int dy)
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
//滑動的模式
mMode = SCROLL_MODE;
//結(jié)束標(biāo)記
mFinished = false;
//彈性滑動時間
mDuration = duration;
//開始時間
mStartTime = AnimationUtils.currentAnimationTimeMillis();
//開始位置
mStartX = startX;
mStartY = startY;
//結(jié)束位置
mFinalX = startX + dx;
mFinalY = startY + dy;
//需要滑動的距離
mDeltaX = dx;
mDeltaY = dy;
//滑動時間的倒數(shù)
mDurationReciprocal = 1.0f / (float) mDuration;
}
4.2、scrollTo 是在 computeScroll() 回調(diào)中調(diào)用的
在調(diào)用 Scroller#startScroll 方法之后键菱,需要再調(diào)用invalidate
方法谬墙,這個方法會讓View進(jìn)行重新繪制,也就
是說會重新的調(diào)用 draw() 方法经备,查閱View#draw()
方法可以看到拭抬,該方法中還調(diào)用 computeScroll 方法,并且該
方法是一個空方法侵蒙,我們重寫該方法造虎,在這個方法中我們捕獲每次 View 重繪的時機(jī),然后再進(jìn)行 scrollTo 滑動即可纷闺。
- View 中的 draw 方法調(diào)用 computeScroll 的代碼片段
...
if (!drawingWithRenderNode) {
computeScroll();//繪制時算凿,調(diào)用該方法
sx = mScrollX;
sy = mScrollY;
}
...
4.3份蝴、View中的computeScroll方法,它是一個空方法氓轰,源碼如下婚夫,它的源碼大概的意思如下:該方法用于在彈性滑動時更新 View 的 scrollX 和 scrollY 的值。
/**
* 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() {
}
然而在每次調(diào)用 srcollTo 進(jìn)行滑動之后署鸡,我們總得知道什么時候滑動到終點(diǎn)啊和下次要滑動的位置案糙?這個時候就得了解另一個方法
Scroller#computeScrollOffset()
。
4.4储玫、computeScrollOffset 分析
從方法解釋中可以大概的知道:
1.方法返回 true 表示滑動還沒結(jié)束侍筛,返回 false 代表滑動已經(jīng)結(jié)束,如果沒有結(jié)束撒穷,獲取計算后的 scrollX 和 scrllY 的值進(jìn)行再次的滑動匣椰。
2.該方法可以計算滑動后的位置 scrollX 和 scrollY 的值,也就是 mCurrX 和 mCurrY 的值端礼,供下次 View#scroll(scrollX,scrollY) 使用禽笑。
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*
*/
public boolean computeScrollOffset() {
if (mFinished) {//使用 mFinished 標(biāo)記滑動是否結(jié)束
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
//timePassed * mDurationReciprocal
//timePassed:當(dāng)前時間 - 開始時間
//mDurationRecipracal:1f/mDuration
//根據(jù)流逝的時間計算出一個百分比值
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
//更新 mCurrX 和 mCurrY 的值
mCurrX = mStartX + Math.round(x * mDeltaX);//四舍五入操作
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE://這種方式暫時不分析。
...
break;
}
}
else {//滑動時間到
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
5蛤奥、總結(jié)
Scroller 是用于解決 View#scrollTo/scrollBy() 滑動效果問題佳镜,借助它可以實(shí)現(xiàn)彈性滑動效果。實(shí)現(xiàn)原理簡單來說就是 View 內(nèi)容在可以在一段時間內(nèi)完成一段距離的滑動凡桥,在這段時間內(nèi)不斷地通過回調(diào)去調(diào)用 View#scrollTo 方法蟀伸,從而實(shí)現(xiàn)彈性滑動效果。
以上是個人對 Scroller 的學(xué)習(xí)筆記缅刽,有任何不對之處望大家批評指正啊掏。