Android Launcher分析和修改6——頁面滑動(PagedView)


  本來打算分析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 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM