众所周知(我就当你们都了解了 (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这个函数中要用到的。
而当MotionEvent 为 MotionEvent.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).currentPage与mCurItem相同或大于时:如果滑动时,而滑动距离不足以使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,即scrollOffset在leftBound与rightBound之间这个方法会返回mItem[0]也就是比当前的mCurItem小了(这里的mItem会始终维持length为3)。
上述代码中的offset由calculatePageOffsets()提供,简单来讲,当在第一个page的时候这个page的offset为0.0,第二个page为1.0,以此类推...
offset是由calculatePageOffsets提供的,是由当前页面的offset加减widthFactor和marginOffset来得出的。
总结:
1.ViewPager在较快的滑动速度下只要移动距离超过 25dp 就会切换到下一个页面。
2.ViewPager在较慢的滑动速度下如果当前页面的偏移量大于 0.6 就会切换到下一个页面。
好了,今天就先写到这,谢谢你能读完,下次会更新ViewPager的懒加载相关,如果有什么写的不好的或者错误还请及时指出,Thanks!