android_Scroller 彈性滑動源碼分析

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í)筆記缅刽,有任何不對之處望大家批評指正啊掏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市衰猛,隨后出現(xiàn)的幾起案子迟蜜,更是在濱河造成了極大的恐慌,老刑警劉巖啡省,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜睛,死亡現(xiàn)場離奇詭異,居然都是意外死亡卦睹,警方通過查閱死者的電腦和手機(jī)畦戒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來结序,“玉大人兢交,你說我怎么就攤上這事×裕” “怎么了配喳?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵酪穿,是天一觀的道長。 經(jīng)常有香客問我晴裹,道長被济,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任涧团,我火速辦了婚禮只磷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泌绣。我一直安慰自己钮追,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布阿迈。 她就那樣靜靜地躺著元媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苗沧。 梳的紋絲不亂的頭發(fā)上刊棕,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音待逞,去河邊找鬼甥角。 笑死,一個胖子當(dāng)著我的面吹牛识樱,可吹牛的內(nèi)容都是我干的嗤无。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼怜庸,長吁一口氣:“原來是場噩夢啊……” “哼当犯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起休雌,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肝断,沒想到半個月后杈曲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胸懈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年担扑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趣钱。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡涌献,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出首有,到底是詐尸還是另有隱情燕垃,我是刑警寧澤枢劝,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站卜壕,受9級特大地震影響您旁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轴捎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一鹤盒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侦副,春花似錦侦锯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汇竭,卻和暖如春葱蝗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背细燎。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工两曼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玻驻。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓悼凑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親璧瞬。 傳聞我的和親對象是個殘疾皇子户辫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)容是博主照著書敲出來的,博主碼字挺辛苦的嗤锉,轉(zhuǎn)載請注明出處渔欢,后序內(nèi)容陸續(xù)會碼出。 當(dāng)了解了Android坐標(biāo)系和觸...
    Blankj閱讀 6,650評論 3 61
  • 說起View的滑動效果瘟忱,實(shí)現(xiàn)的方法有多種奥额,例如使用動畫,或者通過改變View的布局參數(shù)访诱,其實(shí)除了這兩種外垫挨,在And...
    SheHuan閱讀 20,023評論 11 109
  • 導(dǎo)語 滑動算是Android比較常用的效果了,滑動的操作具有很好的用戶體驗(yàn)性触菜。 主要內(nèi)容 滑動效果是如何產(chǎn)生的 實(shí)...
    一個有故事的程序員閱讀 6,456評論 3 11
  • 什么是View View 是 Android 中所有控件的基類九榔。 View的位置參數(shù) View 的位置由它的四個頂...
    acc8226閱讀 1,179評論 0 7
  • 《白鹿原》里官兵滿街追著白嘉軒一行人剪辮子的畫面挺有意思的,想起清廷入關(guān)之后“留頭不留發(fā),留發(fā)不留頭”的禁令哲泊,辮子...
    清風(fēng)閣下閱讀 439評論 0 2