posts - 83, comments - 242, trackbacks - 0, articles - 0
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Android Launcher分析和修改6——页面滑动(PagedView)

Posted on 2013-07-05 19:32  泡泡糖  阅读(12354)  评论(0编辑  收藏

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