本来打算分析CellLayout的源码,不过因为它们之间是容器包含关系,所以打算先把PagedView分析。PagedView代码很多,今天主要是分析跟核心功能相关的代码。PagedView主要实现一个功能——页面滑动。
PagedView继承了ViewGroup类,是一个容器类,可以包含第三方的View,实际上Launcher里面的PagedView主要就是包含了CellLayout的显示。对于一个View类来说,我们触摸屏幕界面,首先会触发View类的onInterceptTouchEvent()回调函数。这个函数负责处理原始的消息驱动,决定是拦截消息还是传给上层的View。这个涉及Android的事件驱动的原理。不了解的朋友可以查阅相关资料,这里不多说。
下面是PagedView的消息流程传递流程图:
/*
* 这个方法主要是决定触摸消息如何是否需要往上层传递。
* 如果返回为true,则会调用PagedView类的onTouchEvent方法,处理触摸事件。否则传给上层View
*/
public boolean onInterceptTouchEvent(MotionEvent ev) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent Enter chlid="+getChildCount());
//获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更新速度值。 OWL acquireVelocityTrackerAndAddMovement(ev); //没有页面,直接跳过给父类处理。 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); /* * 最常见的需要拦截的情况:用户已经进入滑动状态,而且正在移动手指滑动 * 对这种情况直接进行拦截,调用PagedView的onTouchEvent() */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent move scrolling..."); //截断触摸反馈,直接触发onToucEvent,滑动过程中。 return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_MOVE..."); /* * 当在这里接受到ACTION_MOVE时,说明mTouchState!=TOUCH_STATE_SCROLLING * 并且mIsBeingDragged的值应该为false, * 否则DragLayer就应该截获了MotionEvent用于实现拖拽。 * 此时还没有进入滑动状态,当mActivePointerId == INVALID_POINTER时, * 也就是在此之前没有接收到任何touch事件。 * 这种情况发生在Workspace变小时,也就是之前Workspace处于SPRING_LOADED状态。 * 当出现这种情况时直接把当前的事件当作ACTION_DOWN进行处理。 * 反之,则通过determineScrollingStart()尝试能够进入滑动状态。 */ if (mActivePointerId != INVALID_POINTER) //已经发生触摸,获取到触点OWL { determineScrollingStart(ev); break; } } case MotionEvent.ACTION_DOWN: { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_DOWN..."); final float x = ev.getX(); final float y = ev.getY(); //记录点击的位置的坐标以及触点的记录(多点触摸识别) mDownMotionX = x; mLastMotionX = x; mLastMotionY = y; mLastMotionXRemainder = 0; mTotalMotionX = 0; mActivePointerId = ev.getPointerId(0); //第一个触点,返回0 mAllowLongPress = true; if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent mDownMotionX="+mDownMotionX+"
mLastMotionY="+mLastMotionY +" mActivePointerId="+mActivePointerId); /* * 判断目前是真正滑动页面还是已经滑动结束,如果是真正滑动会拦截消息 */ final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); if (finishedScrolling) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent finishedScrolling.."); mTouchState = TOUCH_STATE_REST; mScroller.abortAnimation(); } else //按下拖动应该属于滑动状态 { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent TOUCH_STATE_SCROLLING.."); mTouchState = TOUCH_STATE_SCROLLING; } // 识别是否触摸状态是否是直接翻页状态,如果是直接翻页,在onTouchEvent里面会直接调用 // snapToPage()方法,跳到目标页面 if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { if (getChildCount() > 0) { if (hitsPreviousPage(x, y)) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent hitsPreviousPage true..."); //点击上一页 mTouchState = TOUCH_STATE_PREV_PAGE; } else if (hitsNextPage(x, y)) { if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent hitsNextPage true..."); mTouchState = TOUCH_STATE_NEXT_PAGE; } if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent no next or pre page.."); } } break; } //不需要拦截触摸消息 case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_UP || ACTION_CANCEL"); mTouchState = TOUCH_STATE_REST; mAllowLongPress = false; mActivePointerId = INVALID_POINTER; releaseVelocityTracker(); break; case MotionEvent.ACTION_POINTER_UP: if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_POINTER_UP"); onSecondaryPointerUp(ev); releaseVelocityTracker(); break; } if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent mTouchState="+mTouchState); /*. * 只要是mTouchState的状态不为TOUCH_STATE_REST,那么就进行事件拦截,调用onTouchEvent */ return mTouchState != TOUCH_STATE_REST; }
上面是onInterceptTouchEvent的代码,上面已经给了很详细的注释。整个函数主要就是判断TouchState是什么状态。PagedView类定义了4种TouchEvent的状态。
//滑动结束状态
protected final static int TOUCH_STATE_REST = 0;
//正在滑动 protected final static int TOUCH_STATE_SCROLLING = 1;
//滑动到上一页 protected final static int TOUCH_STATE_PREV_PAGE = 2;
//滑动到下一页 protected final static int TOUCH_STATE_NEXT_PAGE = 3;
除非是TOUCH_STATE_REST状态,否则都会进行消息拦截。拦截后的消息会在onInterceptTouchEvent的onTouchEvent里面处理。主要都是有关滑动状态的消息。其他点击按下的消息在会传递到上一层的View控件。再进行类似的处理。下面我们看看onTouchEvent处理的事情,因为PagedView的onTouchEvent代码比较多,我这里就不全部贴上来,只把关键处理的部分代码贴出来,主要是页面切换。所有的最终操作都是在ACTION_UP里面进行处理。也就是说需要等你把手指离开了屏幕,才会最后决定是否需要切换页面。
下面给出ACTION_UP的关键代码:
public boolean onTouchEvent(MotionEvent ev) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent entering.."); //........switch (action & MotionEvent.ACTION_MASK) { //.........此处省略ACTION_DOWN和ACTION_MOVE case MotionEvent.ACTION_UP: if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.."); if (mTouchState == TOUCH_STATE_SCROLLING) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING"); final int activePointerId = mActivePointerId; final int pointerIndex = ev.findPointerIndex(activePointerId); final float x = ev.getX(pointerIndex); final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); //横向速率 int velocityX = (int) velocityTracker.getXVelocity(activePointerId); //移动距离 final int deltaX = (int) (x - mDownMotionX); //页面宽 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); //移动距离超过页面宽40% boolean isSignificantMove = Math.abs(deltaX) > pageWidth *SIGNIFICANT_MOVE_THRESHOLD; final int snapVelocity = mSnapVelocity; mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); //根据滑动距离和速率,判断是否是滑动 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && Math.abs(velocityX) > snapVelocity; // 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。 //我们使用一个阀值来判断是进行翻页还是返回到初始页面 boolean returnToOriginalPage = false; //Math.signum 函数将正数转换为 1.0,将负数转换为 -1.0,0 仍然是 0。 实际上,它只是提取一个数的符号 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && Math.signum(velocityX) != Math.signum(deltaX) && isFling) { //返回当前页面 returnToOriginalPage = true; } int finalPage;//页面朝左移动 if (((isSignificantMove && deltaX > 0 && !isFling) || (isFling && velocityX > 0)) && mCurrentPage > 0) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING LeftSnap"); finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; snapToPageWithVelocity(finalPage, velocityX); } //页面朝右移动 else if (((isSignificantMove && deltaX < 0 && !isFling) || (isFling && velocityX < 0)) && mCurrentPage < getChildCount() - 1) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING RightSnap"); finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; snapToPageWithVelocity(finalPage, velocityX); } else { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING Back Origation"); //寻找离屏幕中心最近的页面移动 snapToDestination(); } } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_PREV_PAGE"); //直接切换到上一页,没有进行滑动 int nextPage = Math.max(0, mCurrentPage - 1); if (nextPage != mCurrentPage) { snapToPage(nextPage); } else { snapToDestination(); } } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_NEXT_PAGE"); //直接切换到下一页 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); if (nextPage != mCurrentPage) { snapToPage(nextPage); } else { snapToDestination(); } } else { if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. onUnhandledTap()");
//这里是空回调,继承PagedView的方法,会重载这方法处理一些操作。 onUnhandledTap(ev); } mTouchState = TOUCH_STATE_REST; mActivePointerId = INVALID_POINTER; releaseVelocityTracker(); break;return true; }
从上面我们可以看到,onTouchEvent的ACTION_UP主要是处理了3种情况,分别对应TOUCH_STATE_SCROLLING、
TOUCH_STATE_PREV_PAGE和TOUCH_STATE_NEXT_PAGE 3种不同的滑动情况。
我们接下来看看但我们滑动界面的时候,触摸消息如何分配:
上面是我向右滑动的时候,Logcat打印的消息,上面代码都加了打印消息,可以对应着看。我们可以看到,首先触发的是onInterceptTouchEvent()方法,而且触发过程中,mTouchState的状态发生变化,从TOUCH_STATE_REST到TOUCH_STATE_SCROLLING。也就是说从刚开始不会触发onTouchEvent到后面会直接拦截触摸消息,除非PagedView的onTouchEvent。onTouchEvent也是经历了几种状态变化,最后在ACTION_UP的时候,按照TOUCH_STATE_SCROLLING的状态进行处理,最后调用snapToPageWithVelocity()方法,跳转到下一页。
下面我们在看看snapToPageWithVelocity如何跳转到其他页面:
protected void snapToPageWithVelocity(int whichPage, int velocity) { //..............
//根据滑动的速度,计算一个切换动画的显示时间,速度越快,动画显示时间越短。 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); //跳转到指定页面。 snapToPage(whichPage, delta, duration); }
里面还是调用了snapToPage的方法,只是多了一个处理滑动速度的方法,滑动速度在onTouchEvent里面启动了一个记录滑动速度的类VelocityTracker类实现记录滑动的速度。这里会根据用户滑动的速度急速页面切换的动画时间。
protected void snapToPage(int whichPage, int delta, int duration)
{ mNextPage = whichPage; //............ //切换的时候调用Scroller的startScroll进行切换 if (!mScroller.isFinished()) mScroller.abortAnimation(); mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); notifyPageSwitchListener(); invalidate(); }
当系统调用startScroll()的时候,会自动回调computeScroll()方法,而在computeScroll()方法里面,会调用scrollTo()方法进行切换,并且会调用invalidate()方法一直刷新界面,形成动画效果和页面切换效果。
protected boolean computeScrollHelper() {
//computeScrolloffset用来计算滑动是否结束,当你调用startScroll()方法的时候这个方法返回的值一直都为true,
//如果采用其它方式移动视图比如:scrollTo()或 scrollBy时那么这个方法返回false if (mScroller.computeScrollOffset()) { if (mScrollX != mScroller.getCurrX() || mScrollY != mScroller.getCurrY()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); }
//刷新界面 invalidate(); return true; } //............. return false; }
到这里基本就追踪到页面最后滑动切换的实际操作过程。scrollTo是View类的方法,PagedView类把它重写了,主要是做了一些位置的判断操作。更新X、Y坐标,然后调用父类的scrollTo方法。除了这些页面操作外,PagedView类还有很多其他的辅助方法,后面有时间,我会再分析,不过掌握Launcher的PagedView类,你刚开始只需要弄清楚滑动切换的功能就可以,毕竟这个类最主要的功能就是这个。
今天就说到这里,下一篇文章会分析workspace或者cellLayout。到时候看情况分析!
系列文章:
Android Launcher分析和修改1——Launcher默认界面配置(default_workspace)
Android Launcher分析和修改2——Icon修改、界面布局调整、壁纸设置
Android Launcher分析和修改3——Launcher启动和初始化
Android Launcher分析和修改4——初始化加载数据
Android Launcher分析和修改5——HotSeat分析
Edited by mythou
原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3174032.html
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>