实现一个可定制化的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 看起来一般般,建议运行看效果):
在这里插入图片描述

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