Android 4.0 Launcher2源码分析——Workspace滑动

转自http://blog.csdn.net/chenshaoyang0011/article/details/7845434

本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!

文中如有纰漏之处,望不吝指教~~~欢迎讨论,共同学习~~~

Launcher桌面的一大功能就是支持左右滑动,这样的功能在现在的应用中使用非常广泛,并且有很多实现的方式,可以通过使用Fragment来实现也可以通过自定义的控件来实现。

Launcher采用了后者,这一功能的实现在Workspace来完成。首先来看一下Workspace的继承关系:


从图中可以看出WorkspacePagedView的子类,而实际上滑动功能的实现是在PagedView中实现的。在Launcher中,Workspace中有五个CellLayout,分别代表五个分屏。

当左右拖动CellLayout时,就能实现滑动的效果。但实际上CellLayout中还有其他的子ViewPagedView是如何避免了来自子View的干扰的呢?这里就需要讨论另一个问题,Androidtouch事件的拦截制度。而拦截发生在ViewGrouponInterceptTouchEvent()onTouchEvent()以及ViewonTouchEvent中。当发生touch事件时,系统会产生一个MotionEvent并且沿着View Tree开始传递。首先获取MotionEventView Tree的根节点,根节点通常是一个ViewGroupViewGroup将在onInterceptTouchEvent()中获取MotionEvent并决定是否继续向下传递。当在ViewGroup.onInterceptEvent()中返回true时,将截获MotionEventView Tree下面的View将无法获得MotionEvent转而交给当前ViewGrouponTouchEvent()方法。如果onTouchEvent中返回false,那么MotionEvent将沿着View Tree向上传给上一层。拦截的过程草图如下:




有了touch事件的拦截机制之后,View tree中的各个层之间的分工也就更加明确了。在Launcher的View tree中,从上到下的主要的节点有,DragLayer,Workspace,CellLayout。DragLayer层的主要任务是负责对图标和AppWidget进行拖拽,Workspace则主要负责左右滑动,CellLayout则用于容纳各种桌面的快捷方式。大概的分工如下:



那么现在就进入PagedView中去了解实现的过程吧。

滑动功能主要分两步:1、在onInterceptTouchEvent中进行拦截。2、在onTouchEvent中进行滑动。


1,onInterceptTouchEvent(MotionEvent en)

在这个方法中,决定了什么时候截获MotionEvent来实现滑动,避免了子View的其他事件的影响(如点击事件)。

[java]  view plain copy
  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.      /**  
  3.      * This method JUST determines whether we want to intercept the motion. 
  4.      * If we return true, onTouchEvent will be called and we do the actual 
  5.      * scrolling there. 
  6.      **/  
  7.        
  8.     //获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更行速度值。  
  9.     acquireVelocityTrackerAndAddMovement(ev);  
  10.     ......  
  11.      /** 
  12.      * Shortcut the most recurring case: the user is in the dragging 
  13.      * state and he is moving his finger.  We want to intercept this 
  14.      * motion. 
  15.      * 最常见的需要拦截的情况:用户已经进入滑动状态,并且正在滑动手指。 
  16.      * 对这种情况直接进行拦截,执行onTouchEvent()继续执行滑动操作。 
  17.      **/  
  18.     final int action = ev.getAction();  
  19.     if ((action == MotionEvent.ACTION_MOVE) &&  
  20.             (mTouchState == TOUCH_STATE_SCROLLING)) {  
  21.         return true;  
  22.     }  
  23.   
  24.     switch (action & MotionEvent.ACTION_MASK) {  
  25.         case MotionEvent.ACTION_MOVE: {  
  26.               
  27.             /** 
  28.              *  mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
  29.              * whether the user has moved far enough from his original down touch. 
  30.              */  
  31.             /** 
  32.              * 当在这里接受到ACTION_MOVE时,说明mTouchState!=TOUCH_STATE_SCROLLING并且mIsBeingDragged的值应该为false, 
  33.              * 否则DragLayer就应该截获了MotionEvent用于实现拖拽。 
  34.              * 此时还没有进入滑动状态,当mActivePointerId == INVALID_POINTER时,也就是在此之前没有接收到任何touch事件。 
  35.              * 这种情况发生在Workspace变小时,也就是之前Workspace处于SPRING_LOADED状态。当出现这种情况时直接把当前的事件当作ACTION_DOWN进行处理。 
  36.              * 反之,则通过determineScrollingStart()尝试能够进入滑动状态。 
  37.              */  
  38.             if (mActivePointerId != INVALID_POINTER) {  
  39.                 determineScrollingStart(ev);  
  40.                 break;  
  41.             }  
  42.             // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN  
  43.             // event. in that case, treat the first occurence of a move event as a ACTION_DOWN  
  44.             // i.e. fall through to the next case (don't break)  
  45.             // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events  
  46.             // while it's small- this was causing a crash before we checked for INVALID_POINTER)  
  47.         }  
  48.   
  49.         case MotionEvent.ACTION_DOWN: {  
  50.             final float x = ev.getX();  
  51.             final float y = ev.getY();  
  52.             // Remember location of down touch  
  53.             //记录按下的x的坐标值  
  54.             mDownMotionX = x;  
  55.             //记录前次发生touch时的坐标  
  56.             mLastMotionX = x;  
  57.             mLastMotionY = y;  
  58.             //因为在ScrollBy时只能使用int,而记录的x和y都是float,会产生误差,故这里用mLastMotionXRemainder记录余数  
  59.             //用于消除误差  
  60.             mLastMotionXRemainder = 0;  
  61.             //x方向上的总位移  
  62.             mTotalMotionX = 0;  
  63.             mActivePointerId = ev.getPointerId(0);  
  64.               
  65.             //设置mAllowLongPress=true,允许LongClick事件发生。LongClick事件定义在Launcher中  
  66.             //处理的内容包括启动对shortcut的拖拽或弹出壁纸选择的对话框,若mAllowLongPress=false,  
  67.             //则不会响应以上事件。  
  68.             mAllowLongPress = true;  
  69.               
  70.             /**  
  71.              * If being flinged and user touches the screen, initiate drag; 
  72.              * otherwise don't.  mScroller.isFinished should be false when 
  73.              * being flinged. 
  74.              * 当屏幕处于flinged状态(快速滑动)时,若此时用户触摸了屏幕,需要使滑动停止。 
  75.              * 并且初始化拖拽的条件 
  76.              **/  
  77.             final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());  
  78.             final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);  
  79.             if (finishedScrolling) {  
  80.                 mTouchState = TOUCH_STATE_REST;  
  81.                 mScroller.abortAnimation();  
  82.             } else {  
  83.                 mTouchState = TOUCH_STATE_SCROLLING;  
  84.             }  
  85.   
  86.             // check if this can be the beginning of a tap on the side of the pages  
  87.             // to scroll the current page  
  88.             if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {  
  89.                 if (getChildCount() > 0) {  
  90.                     if (hitsPreviousPage(x, y)) {  
  91.                         mTouchState = TOUCH_STATE_PREV_PAGE;  
  92.                     } else if (hitsNextPage(x, y)) {  
  93.                         mTouchState = TOUCH_STATE_NEXT_PAGE;  
  94.                     }  
  95.                 }  
  96.             }  
  97.             break;  
  98.         }  
  99.   
  100.         case MotionEvent.ACTION_UP:  
  101.         case MotionEvent.ACTION_CANCEL:  
  102.             mTouchState = TOUCH_STATE_REST;  
  103.             mAllowLongPress = false;  
  104.             mActivePointerId = INVALID_POINTER;  
  105.             releaseVelocityTracker();  
  106.             break;  
  107.         case MotionEvent.ACTION_POINTER_UP:  
  108.             onSecondaryPointerUp(ev);  
  109.             releaseVelocityTracker();  
  110.             break;  
  111.     }  
  112.   
  113.     /**  
  114.      * The only time we want to intercept motion events is if we are in the 
  115.      * drag mode. 
  116.      * 只有进入了滑动状态,才进行拦截,进入onTouchEvent执行滑动操作。当mTouchState != TOUCH_STATE_REST 
  117.      * 时,就说明没有进入滑动状态。 
  118.      **/  
  119.     return mTouchState != TOUCH_STATE_REST;  
  120. }  

代码的主要工作是根据MotionEvent和当前的状态,在各个状态中切换mTouchState。其中一共有四个状态值,TOUCH_STATE_RESTTOUCH_STATE_SCROLLINGTOUCH_STATE_PREV_PAGETOUCH_STATE_NEXT_PAGE。状态之间的切换可以用如下的草图表示:



当mTouchState==TOUCH_STATE_REST时,不需要任何滑动操作,将MotionEvent向子View传递。这里,分析一种最简单的情况:初始桌面静止,手指按下,然后开始左右滑动。通过这种简单情况的分析,来帮助理解onInterceptTouceEvent的作用。开始桌面静止,则mTouchState==TOUCH_STATE_REST,触发switch分支中MotionEvent.ACTION_DOWN的代码。记录按下点的坐标,设置mAllowLongPress=true。由于mTouchState=TOUCH_STATE_REST,所以动作被传向了子View。接下来,在长按事件被触发之前移动手指则会在代码中调用determineScrollingStart()来决定是否进入滑动状态。进入滑动状态之后mTouchState的值就变为TOUCH_STATE_SCROLLING,然后onTouchEvent中的操作就会被用,开始滑动。

总结一下onInterceptTouchEvent()的工作就是:切换PagedView的状态,如果处于滑动状态就拦截。



2,onTouchEvent(MotionEvent en)

在这个方法中,执行各种关于滑动的工作的计算,界面的刷新等工作。

[java]  view plain copy
  1. public boolean onTouchEvent(MotionEvent ev) {  
  2.         ......  
  3.         switch (action & MotionEvent.ACTION_MASK) {  
  4.         case MotionEvent.ACTION_DOWN:  
  5.             /* 
  6.              * If being flinged and user touches, stop the fling. isFinished 
  7.              * will be false if being flinged. 
  8.              */  
  9.             /** 
  10.              * 如果Workspace此时已经被“掷出去”(靠惯性滑动)。 
  11.              * 此时发生ACTION_DOWN则需要停止滑动。 
  12.              */  
  13.             if (!mScroller.isFinished()) {  
  14.                 mScroller.abortAnimation();  
  15.             }  
  16.   
  17.             // Remember where the motion event started  
  18.             mDownMotionX = mLastMotionX = ev.getX();  
  19.             mLastMotionXRemainder = 0;  
  20.             mTotalMotionX = 0;  
  21.             mActivePointerId = ev.getPointerId(0);  
  22.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  23.                 pageBeginMoving();  
  24.             }  
  25.             break;  
  26.   
  27.         case MotionEvent.ACTION_MOVE:  
  28.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  29.                 ......  
  30.                 if (Math.abs(deltaX) >= 1.0f) {  
  31.                     ......  
  32.                     if (!mDeferScrollUpdate) {  
  33.                         //调用scrollBy滑动桌面  
  34.                         scrollBy((int) deltaX, 0);  
  35.                         ......  
  36.                     } else {  
  37.                         ......  
  38.                     }  
  39.                     mLastMotionX = x;  
  40.                     mLastMotionXRemainder = deltaX - (int) deltaX;  
  41.                 } else {  
  42.                     awakenScrollBars();  
  43.                 }  
  44.             } else {  
  45.                 /** 
  46.                  * 如果条件满足,则进入滑动状态,开始滑动。 
  47.                  */  
  48.                 determineScrollingStart(ev);  
  49.             }  
  50.             break;  
  51.         case MotionEvent.ACTION_UP:  
  52.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  53.                 ......  
  54.                 boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;  
  55.   
  56.                 boolean returnToOriginalPage = false;  
  57.                 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));  
  58.                 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&  
  59.                         Math.signum(velocityX) != Math.signum(deltaX)) {  
  60.                     returnToOriginalPage = true;  
  61.                 }  
  62.   
  63.                 //当速率超过snapVelocity或者总的移动距离超过MIN_LENGTH_FOR_FLING  
  64.                 //则判定isFling=true  
  65.                 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&  
  66.                         Math.abs(velocityX) > snapVelocity;  
  67.   
  68.                 int finalPage;  
  69.                   
  70.                 //判断拿起手指之后应该进入哪个分屏  
  71.                 if (((isSignificantMove && deltaX > 0 && !isFling) ||  
  72.                         (isFling && velocityX > 0)) && mCurrentPage > 0) {  
  73.                     finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;  
  74.                     snapToPageWithVelocity(finalPage, velocityX);  
  75.                 } else if (((isSignificantMove && deltaX < 0 && !isFling) ||  
  76.                         (isFling && velocityX < 0)) &&  
  77.                         mCurrentPage < getChildCount() - 1) {  
  78.                     finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;  
  79.                     snapToPageWithVelocity(finalPage, velocityX);  
  80.                 } else {  
  81.                     snapToDestination();  
  82.                 }  
  83.             } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {  
  84.                 // at this point we have not moved beyond the touch slop  
  85.                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so  
  86.                 // we can just page  
  87.                 //直接进入前一屏  
  88.                 int nextPage = Math.max(0, mCurrentPage - 1);  
  89.                 if (nextPage != mCurrentPage) {  
  90.                     snapToPage(nextPage);  
  91.                 } else {  
  92.                     snapToDestination();  
  93.                 }  
  94.             } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {  
  95.                 // at this point we have not moved beyond the touch slop  
  96.                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so  
  97.                 // we can just page  
  98.                 //直接进入后一屏  
  99.                 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);  
  100.                 if (nextPage != mCurrentPage) {  
  101.                     snapToPage(nextPage);  
  102.                 } else {  
  103.                     snapToDestination();  
  104.                 }  
  105.             } else {  
  106.                 onUnhandledTap(ev);  
  107.             }  
  108.             mTouchState = TOUCH_STATE_REST;  
  109.             mActivePointerId = INVALID_POINTER;  
  110.             releaseVelocityTracker();  
  111.             break;  
  112.   
  113.         case MotionEvent.ACTION_CANCEL:  
  114.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  115.                 snapToDestination();  
  116.             }  
  117.             mTouchState = TOUCH_STATE_REST;  
  118.             mActivePointerId = INVALID_POINTER;  
  119.             releaseVelocityTracker();  
  120.             break;  
  121.         case MotionEvent.ACTION_POINTER_UP:  
  122.             onSecondaryPointerUp(ev);  
  123.             break;  
  124.         }  
  125.         return true;  
  126.     }  

在onTouchEvent中,对接受到的不同的事件进行了分类的处理,大致可以将功能分类为:1、当接受到ACTION_DOWN时,若滑动正在进行,则停止。

2、当接受到ACTION_MOVE时,根据当前的状态调用scrollBy进行滑动或则调用determineScrollingStart准备开始滑动。

3、当接受到ACTION_UP时,根据当前所滑动的位移和速度,判断松手后进入到哪一个分屏。

到这里,Workspace滑动过程的基本流程就介绍完毕了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值