众所周知(我就当你们都了解了 (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
就会切换到下一个页面。
最后
如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!
网友评论