前几天我本来打算从源码的角度分析一下viewpager的实现原理,后来证明我还是too young too simple!这个三千多行的代码我两天都没有看完,所以我打算一点一点的蚕食这个控件。今天我们就专门讲解这个类的onTouchEvennt()方法。首先还是源码贴上,但是都有了详细的注释,很容易明白:
public boolean onTouchEvent(MotionEvent ev) {
/** 如果当前view正在被拖拽,那么直接返回true,表明这个事件被消耗了 **/
if (mFakeDragging) {
// A fake drag is in progress already, ignore this real one
// but still eat the touch events.
// (It is likely that the user is multi-touching the screen.)
return true;
}
/** ev.getEdgeFlags()方法只有在 MotionEvent.ACTION_DOWN时设置,当触摸在边界时不消耗事件,返回false **/
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
if (mAdapter == null || mAdapter.getCount() == 0) {
// Nothing to present or scroll; nothing to touch.
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
/** 将当前事件装入到速度追踪器中 **/
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
boolean needsInvalidate = false;
/** action与MotionEvent.ACTION_MASK配合使用,可以获取多点触控事件 **/
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mScroller.abortAnimation();
mPopulatePending = false;
/** 这个方法追踪进去会调用一个有参数的同名方法,方法的作用是移动到当前item,在这里就是一个定位作用 **/
populate();
// Remember where the motion event started
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
/** mActivePointerId为我们按下时的点,如果返回-1说明这个点已经释放了,因此重置resettouche事件 **/
if (pointerIndex == -1) {
// A child has consumed some touch events and put us into an inconsistent
// state.
needsInvalidate = resetTouch();
break;
}
/** pointerIndex是移动的, 拿到当前点的横坐标 **/
final float x = ev.getX(pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = ev.getY(pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG) {
Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
}
if (xDiff > mTouchSlop && xDiff > yDiff) {
/** 当符合拖拽的条件时,改变拖拽标志,并向父控件请求触摸事件 **/
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
/** 设置滚动状态和缓存 **/
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
/** 实现拖拽并重绘视图 **/
needsInvalidate |= performDrag(x);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final float marginOffset = (float) mPageMargin / width;
final int currentPage = ii.position;
/** 计算当前page的偏移量,用来确定需要滚动到当前页还是下一页 **/
final float pageOffset = (((float) scrollX / width) - ii.offset)
/ (ii.widthFactor + marginOffset);
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
/** 计算滑动到哪个页面 **/
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
needsInvalidate = resetTouch();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
scrollToItem(mCurItem, true, 0, false);
needsInvalidate = resetTouch();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {//计算当前点和前一个DOWN点之间的距离,超过一定范围可以认为是多点模式
final int index = ev.getActionIndex();
final float x = ev.getX(index);
mLastMotionX = x;
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP://重置掉多点模式和记录的距离等等
onSecondaryPointerUp(ev);
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
break;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
return true;
}
关于多点触控用到的API:可方便上面代码阅读:
event.getPointerCount():触控点的个数
getPointerId(int pointerIndex):pointerIndex从0到getPointerCount-1,返回一个触摸点的标示
getX(int pointerIndex):通过标示来得到X坐标
getY(int pointerIndex):通过标示来得到Y坐标
MotionEvent.ACTION_POINTER_1_DOWN:第一个触摸点点击事件
MotionEvent.ACTION_POINTER_2_DOWN:第二个触摸点点击事件
MotionEvent.ACTION_POINTER_1_UP:第一个触摸点松开事件
MotionEvent.ACTION_POINTER_2_UP:第二个触摸点松开事件
我对这个方法做了很详细的注释,相信应该不难看懂。通过解读,我们发现其实和我们自定义view时并没有什么太大区别,只是多了一些多点触控的判定以及操作。剩下的还是对viewpager的方法的操作。那么下一篇我们继续分析viewpager的其它方法,争取做到对viewpager的实现原理一清二楚。