Android 手把手進階自定義View(十三)- 滾動速度 VelocityTracker 及滾動計算 Scroller、OverScroller

一、前言


Scroller 和 OverScroller,這兩個是 Android UI 框架下實現滾動效果的最關鍵的類,ScrollView 內部的實現也是使用的 OverScroller,所以熟練的使用這兩個類的相關API,可以讓我們滿足大部分的滾動開發需求。

 

二、VelocityTracker


 VelocityTracker 主要用跟蹤手指在滑動過程中的速度,包括水平和豎直方向的速度。我們來看個實例:

class VelocityTrackerTestView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    //1、創建實例
    private var mVelocityTracker = VelocityTracker.obtain()

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        //2、重置
        if (event.actionMasked == MotionEvent.ACTION_DOWN) {
            mVelocityTracker.clear()
        }
        //3、開始追蹤
        mVelocityTracker.addMovement(event)

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                //...
            }
            MotionEvent.ACTION_MOVE -> {
                //o...
            }
            MotionEvent.ACTION_UP -> {
                //速度 = ( 終點位置(px) - 起點位置(px) )/ 時間段(ms)
                //4、設置時間段
                mVelocityTracker.computeCurrentVelocity(1000)
                //5、獲取x方向、y方向的速度
                //其中getXVelocity、getYVelocity方法的參數是pointerId,用於多指觸控。不考慮多指時,可以不用傳參數
                var xVelocity = mVelocityTracker.getXVelocity(0)
                var yVelocity = mVelocityTracker.getYVelocity(0)
                //...
            }
        }
        return true
    }

    override fun onDetachedFromWindow() {
        //6、當不需要使用時,重置並回收內存
        mVelocityTracker.clear()
        mVelocityTracker.recycle()
        super.onDetachedFromWindow()
    }


}

上面註釋得很明白,就不過多介紹了。VelocityTracker 一般用來判斷當前是否達到一定的滑動速度來觸發 Fling 的效果,這個滑動速度我們可以自己設置,也可以通過系統提供的來獲取:

    private var mViewConfiguration : ViewConfiguration = ViewConfiguration.get(context)
    private var mMaxFlingVelocity = 0
    //觸發fling的速度
    private var mMinFlingVelocity = 0
    
    init {
        mMaxFlingVelocity = mViewConfiguration.scaledMaximumFlingVelocity
        mMinFlingVelocity = mViewConfiguration.scaledMinimumFlingVelocity
    }

 

三、Scroller


在 View 類裏面,有兩個和滾動相關的類 scrollTo() 和 scrollBy。這兩個方法可以實現 View 內容的移動,比如說一個 TextView,如果使用 scrollTo(),那麼移動的是裏面的文字而不是位置,scrollBy() 也是一樣的。那麼爲什麼是移動,不是滾動呢?這是因爲這兩個方法都是瞬間完成,而不是帶有滾動過程的滾動,所以說如果要實現效果比較好的滾動,光靠 View 自帶的方法是不行,還是需要 Scroller。但是 Scroller、OverScroller 並不是控制 View 進行滾動,實際上它們只是一個控件移動軌跡的輔助計算類,如果你想滾動的時候,它能幫你計算什麼時間應該滾到什麼位置,但是滾不滾需要靠我們來實現。所以說滾動位置由 Scrollers 計算出來了,我們在什麼時候滾呢、滾多少呢?這時候就要 View 的一個回調函數 computeScroll() 出馬了。

    /**
     * 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() {
    }

可以看到 View 裏面的 computeScroll() 是空實現。如果我們用 Scroller 實現一個滾動動畫的時候,這個方法就會被 parent 調用來改變 child。所以一般來說,這個方法可以用來更新 mScrollX 和 mScrollY,但是其實不光可以改變這些,我們還能做其他事情。如果我們調用 Scroller.startScroll(int startX, int startY, int dx, int dy),那麼我們就可以在 computeScroll() 裏面執行實際的操作了,就像下面這樣:

    @Override
    public void computeScroll() {
        // 先判斷mScroller滾動是否完成
        if (mScroller.computeScrollOffset()) {
            // 這裏調用View的scrollTo()完成實際的滾動
            scrollTo( mScroller.getCurrX(), mScroller .getCurrY());
            // 必須調用該方法,否則不一定能看到滾動效果
            invalidate();
        }
        super.computeScroll();
    }

Scroller.computeScrollOffset() 方法是來判斷滾動過程是否完成的,如果沒有完成就需要不停的 scrollTo 下去,所以需要在最後加一個invalidate() 來再次觸發 computScroll(),直到滾動已經結束。

下面簡單介紹一下常用的API。

常用API 簡介
computeScrollOffset() 這個就是來判斷當前的滑動動作是否完成的,用法很單一,就是在computeScroll()裏面來做判斷滾動是否完成
getCurrX()、getCurrY() 這個就是獲取當前滑動的座標值,因爲Scrollers只是一個輔助計算類,所以如果我們想獲取滑動時的時時座標,就可以通過這個方法獲得,然後在computeScroll()裏面調用
getFinalX()、getFinalY() 這個是用來獲取最終滑動停止時的座標
isFinished() 用來判斷當前滾動是否結束
startScroll(int startX, int startY, int dx, int dy) 用來開始滾動,這個是很重要的一個觸發computeScroll()的方法,調用這個方法之後,我們就可以在computeScroll裏面獲取滾動的信息,然後完成我們的需要。這個還有一個帶有滾動持續時間的重載函數,可以根據需求自由使用。特別要注意這四個參數,startX和startY是開始的座標位置,正數左上,負數右下,dx、dy同理,當在computeScroll()獲取getCurrX()的時候,變化範圍就與這裏地設置有關。
public void startScroll(int startX, int startY, int dx, int dy) {
    startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}

 

四、OverScroller


OverScroller和Scroller有什麼區別呢?事實上這兩個類都屬於Scrollers,Scroller出現的比較早在API1就有了,OverScroller是在API9才添加上的,所以功能比較完善。Over的意思就是超出,即OverScroller提供了對超出滑動邊界的情況的處理,這兩個類80%的API是一致的,OverScroller比Scroller添加了以下幾個方法:

  • isOverScrolled():返回當前Scroller當前是否返回有效位置
  • springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) :當你想'回彈'到有效的座標範圍內時調用它
  • fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY):基於一個扔手勢開始滾動
  • notifyHorizontalEdgeReached(int startX, int finalX, int overX)
  • notifyVerticalEdgeReached(int startY, int finalY, int overY)

從名字也能看出來,都是對Over功能的支持。

fling 這個方法很重要,如果你想實現彈性滑動,即滑動之後佈局能夠根據移動速度慢慢減速的話,就需要用這個來實現。這裏需要加速度的參數,我們可以通過 VelocityTracker 這個類來獲取然後使用,也可以配合 GestureDetector 的 fling 手勢使用。具體參數函數,其中 minXY、maxXY 代表滑動便捷,overX、overY 表示 x、y 軸超出回彈距離。fling 的使用同樣需要在 computeScroll() 裏進行相應處理

 

五、總結


本篇我們只是簡單介紹一下 Scroller、OverScroller 的用法,下一篇我們將通過一個示例來加深對它的使用和理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章