本來打算分析CellLayout的源碼,不過因為它們之間是容器包含關系,所以打算先把PagedView分析。PagedView代碼很多,今天主要是分析跟核心功能相關的代碼。PagedView主要實現一個功能——頁面滑動。
PagedView繼承了ViewGroup類,是一個容器類,可以包含第三方的View,實際上Launcher里面的PagedView主要就是包含了CellLayout的顯示。對於一個View類來說,我們觸摸屏幕界面,首先會觸發View類的onInterceptTouchEvent()回調函數。這個函數負責處理原始的消息驅動,決定是攔截消息還是傳給上層的View。這個涉及Android的事件驅動的原理。不了解的朋友可以查閱相關資料,這里不多說。
下面是PagedView的消息流程傳遞流程圖:
//Edited by mythou
//http://www.cnblogs.com/mythou/
/* * 這個方法主要是決定觸摸消息如何是否需要往上層傳遞。 * 如果返回為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的狀態。
//Edited by mythou
//http://www.cnblogs.com/mythou/
//滑動結束狀態
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的關鍵代碼:
//Edited by mythou
//http://www.cnblogs.com/mythou/
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如何跳轉到其他頁面:
//Edited by mythou
//http://www.cnblogs.com/mythou/
protected void snapToPageWithVelocity(int whichPage, int velocity) { //..............
//根據滑動的速度,計算一個切換動畫的顯示時間,速度越快,動畫顯示時間越短。 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); //跳轉到指定頁面。 snapToPage(whichPage, delta, duration); }
里面還是調用了snapToPage的方法,只是多了一個處理滑動速度的方法,滑動速度在onTouchEvent里面啟動了一個記錄滑動速度的類VelocityTracker類實現記錄滑動的速度。這里會根據用戶滑動的速度急速頁面切換的動畫時間。
//Edited by mythou
//http://www.cnblogs.com/mythou/
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()方法一直刷新界面,形成動畫效果和頁面切換效果。
//Edited by mythou
//http://www.cnblogs.com/mythou/
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