ViewPager滑动切换条件初探

作者: Android架构师丨小熊 | 来源:发表于2019-08-07 21:08 被阅读14次

众所周知(我就当你们都了解了 (o゚v゚)ノ )当手指从左向右在View上滑动时候会触发:

boolean onInterceptTouchEvent(MotionEvent ev) 
boolean onTouchEvent(MotionEvent ev)

这两个方法,我们需要了解下在滑动距离为多少的时候ViewPager会切换到下一个页面,而在ViewPager中需要关注以下代码:

ViewPager.java 源码
方法名:onInterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent ev) {
    /*
     * This method JUST determines whether we want to intercept the motion.
     * If we return true, onMotionEvent will be called and we do the actual
     * scrolling there.
    */
    //这个是用来检测多点触控的
    final int action = ev.getAction() & MotionEvent.ACTION_MASK;
    // Always take care of the touch gesture being complete.
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        // Release the drag.
        if (DEBUG) Log.v(TAG, "Intercept done!");
        resetTouch();
        //对于ACTION_CANCEL 和 ACTION_UP只重置触摸的相关变量,不拦截
        return false;
    }
    ///省略了一堆代码
    switch (action) {
        //省略了一堆代码
        case MotionEvent.ACTION_DOWN: {
            /*
            * Remember location of down touch.
            * ACTION_DOWN always refers to pointer index 0.
            */
            mLastMotionX = mInitialMotionX = ev.getX();
            mLastMotionY = mInitialMotionY = ev.getY();
            mActivePointerId = ev.getPointerId(0);
            mIsUnableToDrag = false;
            //省略了一堆代码
        }
        //省略了一堆代码
    }
    //省略了一堆代码
}

代码上写的好明白,当手指按下的时候,即action == MotionEvent.ACTION_DOWN的时候注释写明

Remember location of down touch.//记住按下的位置

这时mInitialMotionX 这个变量就记住了手指按下的时候的X坐标,记住这个变量,在onTouchEvent这个函数中要用到的。 而当MotionEventMotionEvent.ACTION_UP 的时候 return false 表示不拦截,就交给 onTouchEvent 这个方法处理。 接下来看下 onTouchEvent 这个函数,我们看下当手指从屏幕上拿起的时候的 ACTION_UP 会做神马操作。

ViewPager.java 源码
方法名:onTouchEvent

case MotionEvent.ACTION_UP:
    if (mIsBeingDragged) {
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        //getXVelocity()这个方法是native的
        //经测试发现:向右滑动时候这个速度是正的,向左滑动的时候是负的
        int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
        mPopulatePending = true;
        final int width = getClientWidth();
        final int scrollX = getScrollX();
        //当前滑动到的位置ItemInfo
        final ItemInfo ii = infoForCurrentScrollPosition();
        final float marginOffset = (float) mPageMargin / width;
        //当前的page
        final int currentPage = ii.position;
        final float pageOffset = (((float) scrollX / width) - ii.offset)/ (ii.widthFactor + marginOffset);
        final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
        final float x = ev.getX(activePointerIndex);
        //这个mInitialMotionX就是按下时的X坐标,这个变量就是手指总共移动的横向距离
        final int totalDelta = (int) (x - mInitialMotionX);
        //需要滑动到的下个page
        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,totalDelta);
        setCurrentItemInternal(nextPage, true, true, initialVelocity);

        needsInvalidate = resetTouch();
    }
    break;

这时看到 setCurrentItemInternal 这个方法,哦这个是设置当前的item的内部方法,所以这个 nextPage 是怎么计算出来的呢? 这个 determineTargetPage 方法代码不多就全贴在这了。

ViewPager.java 源码
方法名:determineTargetPage
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
    int targetPage;
    //如果当手指移动距离大于mFlingDistance并且横坐标的速度大于定义的最小速度mMinimumVelocity时
    if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
        //velocity是负的才会page + 1
        targetPage = velocity > 0 ? currentPage : currentPage + 1;
    } else {
        //滑动后的page是不是原来没滑动的mCurItem?
        final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
        //这里的pageOffset也是一个在[0,1)之间的float
        //int是直接截断的哦
        targetPage = currentPage + (int) (pageOffset + truncator);
    }

    //这个mItem是个啥?
    if (mItems.size() > 0) {
        final ItemInfo firstItem = mItems.get(0);
        final ItemInfo lastItem = mItems.get(mItems.size() - 1);

        // Only let the user target pages we have items for
        // 防止越界的
        targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
    }

    return targetPage;
}

上述的 determineTargetPage 方法简单来讲:

1.当手指在ViewPager上移动时,如果系统检测到的手指移动时的横向移动距离大于ViewPager的定义的最小移动距离并且手指横向移动的平均速度大于ViewPager定义的最小速度,那么ViewPager就会跳转到下个页面。

Viewpager.java
//关于移动的最小值定义
//最小的移动速度
private static final int MIN_FLING_VELOCITY = 400; // dips
//density 为当前设备的像素密度
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);

//最小的移动距离
private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
//density 为当前设备的像素密度
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);

2.如果上述条件不满足:则判断以下变量

currentPage mCurItem pageOffset
当前page由方法infoForCurrentScrollPosition获得 开始滑动之前的页面 当前page的偏移量

:mCurItem在页面移动时是不会变的。

final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = currentPage + (int) (pageOffset + truncator);

上面这行代码表示当前页和当前显示的item作比较,下面将这两种情况复原下:
(1).currentPagemCurItem相同或大于时:如果滑动时,而滑动距离不足以使infoForCurrentScrollPosition() 这个方法获得与mCurItem不同的值,这时就将判断当前页面的 (int) (pageOffset + truncator) 是否为1 :这里int强转是直接截断的哦),也就是说当pageOffset大于等于0.6的时候才会跳转到下个页面,附上个效果图。

: 左滑右滑一样的哦。
(2).currentPage小于mCurItem时,这时只有从左向右滑动的时候才会造成currentPage较小,那么什么情况下才会出现这种情况呢?
首先,来看一组代码:

ViewPager.java
方法:infoForCurrentScrollPosition()
//此处只列出了循环中的部分代码

if (first || scrollOffset >= leftBound) {
    if (scrollOffset < rightBound || i == mItems.size() - 1) {
        return ii;
    }
} else {
    return lastItem;
}
//这里的scrollOffset,rightBound,leftBound,first分别如下:
//视图显示部分的左边缘的px值(就是你滑动的view)/ 屏幕的宽度 
//由于viewPager会在滑动过程中会构建出左右的view,scrollOffset可能会大于1
final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
//mItems中第一个的偏移量和他的宽度系数加上外边距的偏移(第一次循环)
final float rightBound = offset + ii.widthFactor + marginOffset;
//mItems中第一个的偏移量(第一次循环)
final float leftBound = offset;
//第一次循环的默认值,第二次循环变为false
boolean first = true;

所以,要想返回的item小于当前的mCurItem,那么scrollOffset < rightBound必须为true,scrollOffset >= leftBound 也必须为true,即scrollOffsetleftBoundrightBound之间这个方法会返回mItem[0]也就是比当前的mCurItem小了(这里的mItem会始终维持length3)。
上述代码中的offsetcalculatePageOffsets()提供,简单来讲,当在第一个page的时候这个pageoffset为0.0,第二个page为1.0,以此类推...
offset是由calculatePageOffsets提供的,是由当前页面的offset加减widthFactormarginOffset来得出的。

总结:

1.ViewPager在较快的滑动速度下只要移动距离超过 25dp 就会切换到下一个页面。
2.ViewPager在较慢的滑动速度下如果当前页面的偏移量大于 0.6 就会切换到下一个页面。

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!


相关文章

网友评论

    本文标题:ViewPager滑动切换条件初探

    本文链接:https://www.haomeiwen.com/subject/zrtzdctx.html