實現一個可定製化的TabFlowLayout(二) -- 實現滾動和平滑過渡

效果圖

在這裏插入圖片描述

FlowHelper工程源碼

這次,我們來實現第二個模塊,即view 的滾動和使用 Scroller 平滑滾動,在這篇文章中,您將看到:

  1. View 的事件傳遞簡析
  2. ScrollerBy 和 ScrollerTo 的區別,以及使用 Scroller 實現平滑過渡

前面中,我們已經通過 FlowLayout 實現測量和佈局,這次新建一個類 ScrollFlowLayout 是專門實現滾動邏輯

一、View 的事件傳遞

當點擊一個控件的時候,它的向下傳遞過程大致如下: activity --> window – > viewGroud --> view 。當然第一次走的是 disPatchTouchEvent 方法;通過源碼知道,如果我們對 onInterceptTouchEvent 返回true,則父控件接管當前觸摸事件,不再往下傳遞,而是回調自己的 onTouchEvent 方法。

View 的事件傳遞是基操,大家自行查閱啦

那麼,我們就可以在 onInterceptTouchEvent 去這樣寫:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = ev.getX();
                //拿到上次的down座標
                mMoveX = ev.getX();
                break;

            case MotionEvent.ACTION_MOVE:
                float dx = ev.getX() - mLastX;
                if (Math.abs(dx) >= mTouchSlop) {
                    //由父控件接管觸摸事件
                    return true;
                }
                mLastX = ev.getX();
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

二、View 的滾動和 Scroller

在 onTouchEvent 中,拿到了移動的偏移量,那怎麼實現 View 自身的移動呢?
沒錯,就是使用 ScrollerBy 和 ScrollerTo,它們只改變 View 的內容而不會改變 View 的座標 ,這正是我們需要的,需要注意的是,向左滑爲正,向右爲負。

  • ScrollerTo(int x,int y) 絕對座標移動,以原點爲參考點
  • ScrollerBy(int x,int y) 相對座標移動,以上一次座標爲參考點

那麼,onTouchEvent 就可以這樣寫了:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                //scroller 向右爲負,向左爲正
                int dx = (int) (mMoveX - event.getX());
                scrollBy(dx, 0);
                mMoveX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;

        }
        return super.onTouchEvent(event);
    }

效果如下:
在這裏插入圖片描述
嗯,還差邊界判斷,首先,拿到右邊邊界:

mRightBound = child.getRight() + getPaddingRight();

接着,在move中去判斷邊界:

 case MotionEvent.ACTION_MOVE:
     //scroller 向右爲負,向左爲正
     int dx = (int) (mMoveX - event.getX());
     /**
      * 判斷左右邊界
      */
     int scrollX = getScrollX();
     if (scrollX + dx <= 0) {
         scrollTo(0, 0);
         return true;
     }
     if (scrollX + dx >= mRightBound - mScreenWidth) {
         scrollTo(mRightBound - mScreenWidth, 0);
         return true;
     }
     scrollBy(dx, 0);
     mMoveX = event.getX();
     break;

接着,再運行一下:
在這裏插入圖片描述
邊界加上了,但是總感覺有點卡頓,不夠流暢,我們接着優化一下:

三、使用 Scroller 優化滑動卡頓

上面看到,當通過手指按住滑動之後,應該要有個滾動速度;從 Scroller 的 API 中,我們發現可以使用 Scroller 中的 Fling 方法;
先初始化 Scroller

mScroller = new Scroller(context);

而這個橫向的滾動速度怎麼來呢?
可以有兩種,第一個是通過手勢 Gestrue 的 Fling 拿到,還有一種則是 VelocityTracker ;這裏,我們直接用 VelocityTracker 來拿到橫向速度;
首先 VelocityTracker 通過 obtain 來拿到實例,並通過 addMoveMent 拿到 MotionEvent,這樣才能正確的速度:

 if (mVelocityTracker == null) {
    mVelocityTracker = VelocityTracker.obtain();
    }
mVelocityTracker.addMovement(event);

在 up 的時候,拿到橫向速度:

 case MotionEvent.ACTION_UP:
 case MotionEvent.ACTION_CANCEL:

     mVelocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
     int velocityX = (int) mVelocityTracker.getXVelocity();
     if (Math.abs(velocityX) >= mMinimumVelocity) {
         mCurScrollX = getScrollX();
         mScroller.fling(mCurScrollX, 0, velocityX, 0, 0, getWidth(), 0, 0);
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
     }
    break;

其中 mVelocityTracker.getXVelocity() 表示的是 1s 內偏移的像素點;接着再把它賦值給 mScroller.fling();

當調用了 fling 之後,Scroller 就會調用 computeScroll 方法了。在 computeScroll 方法,拿到當前偏移的像素,與上次的對比,即可讓它平滑滾動,如下:

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()){
            int dx = mCurScrollX - mScroller.getCurrX();
            // 超出右邊界,進行修正
            if (getScrollX() + dx >= mRightBound - mScreenWidth) {
                dx = mRightBound - mScreenWidth - getScrollX();
            }

            // 超出左邊界,進行修正
            if (getScrollX() + dx <= 0) {
                dx = -getScrollX();
            }
            scrollBy(dx,0);
            postInvalidate();
        }
    }

最終效果如下 (gif 看起來一般般,建議運行看效果):
在這裏插入圖片描述

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