Launcher桌面点击&长按&拖动事件处理流程分析

时间:2022-04-05 15:49:26

http://blog.csdn.net/yanbin1079415046/article/details/8119054

Android事件处理是android中很重要的一部分内容,而在ADW_Launcher中,android事件处理的重要性同样得到了很好的体现。进入到ADW_Launcher的主页面,为什么点击屏幕空白处没反应,为什么点击桌面元素图标就打开应用程序;为什么长按桌面元素会使得应用程序进入可拖拽状态;为什么长按桌面空白处却弹出”添加控件在桌面”的操作;为什么你在某个桌面元素图标的上面轻轻的滑动一下,将会执行滑动操作而不执行桌面元素的点击操作。今天我们就简单的来说一说这个问题。文章最后给出一个屏幕滑动的例子,效果图如下:

Launcher桌面点击&长按&拖动事件处理流程分析

  以桌面的某一个应用程序为例,通过ADW_Launcher主页面布局文件(launcher.xml)浅析 . 这篇文章我们知道,launcher.xml的View树结构的最顶层是DragLayer,接下来是WorkSpace,然后是CellLayout,最后是BubbleTextView。我们查看源代码可以发现,DragLayer.Java, WorkSpace.java, CellLayout.java(某一屏), BubbleTextView.java(某一个app)都没有实现dispathTouchEvent。也就是说:这四个控件都可以(注意是可以,具体能不能,就要看它的上一级控件有没有拦截了。)接收下一个动作。(比如:DragLayer.java接收完DOWN事件且被控件(可能是它自己)resume消费掉之后,它能继续接收MOVE或者UP事件)。下面我们就分情况来介绍前面提出的问题。

 Android事件处理机制的详细分析可以参看这篇文章:Android FrameWork——Touch事件派发过程详解 .

  这里简单的说一说。一个动作序列一般包括三个事件:DOWN,MOVE,UP。

  而每个事件的执行是有先后顺序的,比如MOVE事件肯定是发生在DOWN事件之后的,而且只有当所有可处理DOWN事件的地方都执行完以后,才开始执行MOVE或者其他的操作。以DOWN事件为例:从最顶层的父控件dispathTouchEvent开始,到某一个控件的onTouchEvent的DOWN事件处理结束,表示DOWN事件就处理完毕了。然后接下来就回到父控件的dispathTouchEvent执行MOVE或者UP事件。

执行顺序:最顶层父控件的dispathTouchEvent(是否接受后续动作【DOWN设置为true,表示接受后续动作】)-------最顶层父控件的onInterceptTouchEvent(是否拦截事件【View Group才有】)---------子层控件的dispathTouchEvent-------子控件的onTouchEvent。它代表的意思是,如果一切"正常执行"(你就理解成全部是默认实现吧),此时事件将交给最底层的那个View的onTouchEvent()方法处理对应的事件。更具体的处理流程可以参考这篇文章,讲的很详细。android 事件处理

 

一:ADW_Launcher事件处理的"源头"-----onInterceptTouchEvent()方法简要概述

  ADW_Launcher的事件处理都是自DragLayer.java开始的,更底层的FrameLayout我们就不管了。前面说过ADW_Launcher的事件处理类中没有重写dispatchTouchEvent,因此我们就从DragLayer的onInterceptTouchEvent()开始说起(实际应该是从dispatchTouchEvent开始的)。DragLayer.java中的onInterceptTouchEvent的代码如下:

[java] view plain copy
  1. ////代码一  
  2. @Override  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  4.     final int action = ev.getAction();  
  5.   
  6.     final float x = ev.getX();  
  7.     final float y = ev.getY();  
  8.   
  9.     switch (action) {  
  10.         case MotionEvent.ACTION_MOVE:  
  11.             System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_MOVE");  
  12.             break;  
  13.   
  14.         case MotionEvent.ACTION_DOWN:  
  15.             System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_DOWN");  
  16.             // Remember location of down touch  
  17.             mLastMotionX = x;  
  18.             mLastMotionY = y;  
  19.             mLastDropTarget = null;//事件重新开始,设置DropTarget,拖动对象为空  
  20.             break;  
  21.   
  22.         case MotionEvent.ACTION_CANCEL:  
  23.             System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_CANCEL");  
  24.         case MotionEvent.ACTION_UP:  
  25.             System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_UP");  
  26.             if (mShouldDrop && drop(x, y)) {  
  27.                 mShouldDrop = false;  
  28.             }  
  29.             endDrag();  
  30.             break;  
  31.     }  
  32.     return mDragging;  
  33. }  


 ====代码一=====

  从代码中可以看到,其中涉及到两个很重要的标识,一个为mShouldDrop,一个mDragging。至于drop(x,y)方法,这个就是桌面元素(应用程序,小控件,文件夹等)移动之后被放下时将要执行的方法。mShouldDrop和mDragging的访问权限都为private,也就是只可能在本类中设置其值,它们的初始值都为false。它们的值是在startDrag()方法中被设置为true的。

关于startDrag()方法:

  startDrag()设置了几个很重要的值:包括正在拖动的元素是什么类型的View,应用程序还是文件夹等等;正在拖动的元素来自于哪个容器,DragSource;正在拖动的元素的信息;以及正在拖动元素的大小变化和长按触碰反应(振动一下)也是在此处实现的。DragLayer.java中startDrag()的调用链如下:Launcher.java中的onLongClick()方法-----mWorkspace.startDrag(cellInfo);------mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);。至于如何拖动的,不是我们本文的重点,童鞋们可以自己去看看,它是ADW_Launcher中很重要的东西。

  二:事件处理流程------从DOWN事件说起

  当用户触碰屏幕的任意位置之后,ADW_Launcher的事件处理流程就此开始,下面我们分步骤来分析这一过程。

  Step1:DragLayer.java的onInterceptTouchEvent()方法处理DOWN事件

  从上面的代码中可以看到,DOWN事件中将用户触碰屏幕的起始位置记录了下来。此时mDragging为其初始值false,即代码 return false,DragLayer不拦截DOWN事件,DOWN事件往下传递。

  Step2:Workspace.java的onInterceptTouchEvent()方法处理DOWN事件

  Workspace.java的onInterceptTouchEvent()方法中DOWN事件部分的代码如下:

[java] view plain copy
  1. ////代码二  
  2. @Override  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  4.     final float x = ev.getX();  
  5.     final float y = ev.getY();  
  6.   
  7.     switch (action) {  
  8.         case MotionEvent.ACTION_MOVE:  
  9.             //...  
  10.             break;  
  11.   
  12.         case MotionEvent.ACTION_DOWN:  
  13.             System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_DOWN");  
  14.             // Remember location of down touch  
  15.             mLastMotionX = x;  
  16.             mLastMotionY = y;  
  17.             mAllowLongPress = true;  
  18.   
  19.             /* 
  20.              * If being flinged and user touches the screen, initiate drag; 
  21.              * otherwise don't.  mScroller.isFinished should be false when 
  22.              * being flinged. 
  23.              */  
  24.             mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;  
  25.             break;  
  26.   
  27.         case MotionEvent.ACTION_CANCEL:  
  28.         case MotionEvent.ACTION_UP:  
  29.             //...  
  30.             break;  
  31.     }  
  32.   
  33.     /* 
  34.      * The only time we want to intercept motion events is if we are in the 
  35.      * drag mode. 
  36.      * 仅仅在拖动模式下才拦截事件 
  37.      */  
  38.     return mTouchState != TOUCH_STATE_REST;  
  39. }  


 ====代码二====

  mScroller.isFinished()的返回值为true,最终return false。表示Workspace也不拦截事件,继续往下传递。

  Step3:CellLayout.java的onInterceptTouchEvent()方法处理DOWN事件

  CellLayout.java的onInterceptTouchEvent()方法的所有代码如下:这里先给出,方便我们后面分析。

[java] view plain copy
  1. ////代码三  
  2. @Override  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  4.     final int action = ev.getAction();  
  5.     final CellInfo cellInfo = mCellInfo;  
  6.   
  7.     if (action == MotionEvent.ACTION_DOWN) {  
  8.         System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_DOWN");  
  9.         final Rect frame = mRect;  
  10.         final int x = (int) ev.getX() + mScrollX;  
  11.         final int y = (int) ev.getY() + mScrollY;  
  12.         final int count = getChildCount();  
  13.         boolean found = false;  
  14.         for (int i = count - 1; i >= 0; i--) {  
  15.             final View child = getChildAt(i);  
  16.   
  17.             if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {  
  18.                 child.getHitRect(frame);  
  19.                 if (frame.contains(x, y)) {  
  20.                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();  
  21.                     cellInfo.cell = child;  
  22.                     cellInfo.cellX = lp.cellX;  
  23.                     cellInfo.cellY = lp.cellY;  
  24.                     cellInfo.spanX = lp.cellHSpan;  
  25.                     cellInfo.spanY = lp.cellVSpan;  
  26.                     cellInfo.valid = true;  
  27.                     found = true;  
  28.                     mDirtyTag = false;  
  29.                     break;  
  30.                 }  
  31.             }  
  32.         }  
  33.   
  34.         //触碰事件发生在哪里,如果在空白处,该值为false;如果在桌面元素上,该值为true  
  35.         mLastDownOnOccupiedCell = found;  
  36.   
  37.         if (!found) {  
  38.             int cellXY[] = mCellXY;  
  39.             pointToCellExact(x, y, cellXY);  
  40.   
  41.             final boolean portrait = mPortrait;  
  42.             final int xCount = portrait ? mShortAxisCells : mLongAxisCells;  
  43.             final int yCount = portrait ? mLongAxisCells : mShortAxisCells;  
  44.   
  45.             final boolean[][] occupied = mOccupied;  
  46.             findOccupiedCells(xCount, yCount, occupied, null);  
  47.   
  48.             cellInfo.cell = null;  
  49.             cellInfo.cellX = cellXY[0];  
  50.             cellInfo.cellY = cellXY[1];  
  51.             cellInfo.spanX = 1;  
  52.             cellInfo.spanY = 1;  
  53.             cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&  
  54.                     cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];  
  55.   
  56.             // Instead of finding the interesting vacant cells here, wait until a  
  57.             // caller invokes getTag() to retrieve the result. Finding the vacant  
  58.             // cells is a bit expensive and can generate many new objects, it's  
  59.             // therefore better to defer it until we know we actually need it.  
  60.   
  61.             mDirtyTag = true;  
  62.         }  
  63.         setTag(cellInfo);  
  64.     } else if (action == MotionEvent.ACTION_UP) {  
  65.         System.out.println(LOG_TAG + "onInterceptTouchEvent----ACTION_UP");  
  66.         cellInfo.cell = null;  
  67.         cellInfo.cellX = -1;  
  68.         cellInfo.cellY = -1;  
  69.         cellInfo.spanX = 0;  
  70.         cellInfo.spanY = 0;  
  71.         cellInfo.valid = false;  
  72.         mDirtyTag = false;  
  73.         setTag(cellInfo);  
  74.     }  
  75.   
  76.     //永远不拦截事件  
  77.     return false;  
  78. }  


  ====代码三====

  其中count的值为当前屏总共有多少个元素(应用程序,小控件,文件夹等)。接下来循环所有的子控件,取出其坐标存放到名为frame的Rect中,如果用户当前的触点x,y在frame矩形框内,表示当前的这个子控件被用户触碰到了,此时将found标识设置true,表示找到的不是"空区域"(没放元素的格子);将mDirtyTag标识设置false,将valid的值设置为true。将mLastDownOnOccupiedCell设置为found的值(该值表示触碰事件发生在哪里,如果在空白处,该值为false;如果在桌面元素上,该值为true)。并且设置该元素的相关信息放到CellLayout的内部类CellInfo中,这个内部类用来临时存放一些信息,在UP事件处理的时候被清空,它包含的主要内容有:View信息(Intent,title等等的信息)以及cellX,cellY,spanX,spanY,screen以及valid值的信息。cellX等变量的含义请看这篇文章:ADW_Launcher主页面布局文件(launcher.xml)浅析

  接下来根据found的值来确定是否要将CellInfo的值设置为默认值。最终onInterceptTouchEvent()将返回false。CellLayout也不拦截事件。接下来的事情就有意思了。下面我们根据用户动作行为的不同分三种情况来讨论。

A:用户触碰屏幕空白处(点击或长按),见step4

B:用户触碰桌面元素图标(点击或长按),见step5

C:用户滑动屏幕,见step6

  Step4:用户触碰屏幕空白处处理流程

  当用户触碰的是屏幕空白处的时候,通过上面的分析我们知道,此时触碰区域显示给用户的就是CellLayout,也就是说此时CellLayout是当前可见屏幕的最底层View。而此时onInterceptTouchEvent已经全部执行完毕,程序将去寻找消化DOWN事件的方法了。于是CellLayout.java的onTouchEvent()就将执行了。但是很遗憾,在CellLayout.java中并没有找到onTouchEvent()的实现。这很正常,因为CellLayout此时已经把该干的干完了。Java的继承机制告诉我们,此处CellLayout使用了父控件的默认实现。这段默认实现的代码估计大家很熟悉了。如下:

[java] view plain copy
  1. ////代码四  
  2.  @Override  
  3. public boolean onTouchEvent(MotionEvent event) {  
  4.     // TODO Auto-generated method stub  
  5.     return super.onTouchEvent(event);  
  6. }  


 ====代码四====

  通过这段代码我们知道,super.onTouchEvent(event)将会被执行。这句代码很重要,如果你重写了onTouchEvent()方法但是不调用super.onTouchEvent(event)的话,那你Activity中的onClick()和onLongClick()方法就要悲剧了。下面就跟着super.onTouchEvent(event)进到代码里去看一看。该方法位于View.java中。其DOWN事件的代码如下:

[java] view plain copy
  1. ////代码五  
  2.  public boolean onTouchEvent(MotionEvent event) {  
  3.     final int viewFlags = mViewFlags;  
  4.     //....  
  5.     //判断当前控件是否可点击,是否可长按  
  6.     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  7.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  8.         switch (event.getAction()) {  
  9.             case MotionEvent.ACTION_UP:  
  10.                 //...  
  11.                 break;  
  12.   
  13.             case MotionEvent.ACTION_DOWN:  
  14.                 if (mPendingCheckForTap == null) {  
  15.                     mPendingCheckForTap = new CheckForTap();  
  16.                 }  
  17.                 mPrivateFlags |= PREPRESSED;  
  18.                 mHasPerformedLongPress = false;  
  19.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  20.                 break;  
  21.   
  22.             case MotionEvent.ACTION_CANCEL:  
  23.                 //...  
  24.                 break;  
  25.   
  26.             case MotionEvent.ACTION_MOVE:  
  27.                 //...  
  28.                 break;  
  29.         }  
  30.         return true;  
  31.     }  
  32.     return false;  
  33. }  


 ====代码五====

下面我们就来分析一下View.java中对DOWN事件的默认处理(为什么叫默认处理呢?对于像我这样的菜鸟来说,其实刚接触应用程序的时候,是很少会自己去重写事件处理方法的,只是简单的setOnClick()和setOnLongClick()。而对于上述两个监听方法的处理,实际就是在这里处理的,所以就叫它默认处理吧。)

  Step4.1:View.java的onTouchEvent()方法处理DOWN事件

  DOWN事件的处理中主要做了两件事:

a、创建一个延时线程CheckForTap。

b、发送这个延时的操作到Message Queue中,此处延时为115ms,延时执行的线程为a中的CheckForTap线程。

  CheckForTap的代码如下:

[java] view plain copy
  1. ////代码六  
  2.  private final class CheckForTap implements Runnable {  
  3.     public void run() {  
  4.         mPrivateFlags &= ~PREPRESSED;  
  5.         mPrivateFlags |= PRESSED;  
  6.         //设置view的不同状态下的背景  
  7.         refreshDrawableState();  
  8.         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
  9.             postCheckForLongClick(ViewConfiguration.getTapTimeout());  
  10.         }  
  11.     }  
  12. }  


 ====代码六====

  关于CheckForTap线程,我的理解是这样的:在CheckForTap被执行之前,也就是115ms以内,屏幕是可以感应到用户的下一个动作行为的。所以这个线程创建的目的就是为了等待用户的DOWN事件之后的下一个行为动作。

  Step4.2:接收用户的下一个动作,从而判断将执行点击还是长按

  用户触碰屏幕,115ms以内,屏幕已经获取到了用户的下一个动作行为(松手了或者还按着没动),主线程将接着往下执行。115ms到了之后,CheckForTap线程的run()方法将执行,它又会去执行postCheckForLongClick()方法,该方法的作用是:创建一个名为CheckForLongPress的线程,并将该线程以 345ms(500-155 )的延时发送到Message Queue。postCheckForLongClick()方法的代码如下:

[java] view plain copy
  1. ////代码七  
  2. private void postCheckForLongClick(int delayOffset) {  
  3. //此处表示还未处理LongPress事件  
  4.     mHasPerformedLongPress = false;  
  5.   
  6.     if (mPendingCheckForLongPress == null) {  
  7.         mPendingCheckForLongPress = new CheckForLongPress();  
  8.     }  
  9.     mPendingCheckForLongPress.rememberWindowAttachCount();  
  10.     postDelayed(mPendingCheckForLongPress,  
  11.             ViewConfiguration.getLongPressTimeout() - delayOffset);  
  12. }  


 ====代码七====

CheckForLongPress线程的代码如下:

[java] view plain copy
  1. ////代码七.一  
  2.  class CheckForLongPress implements Runnable {  
  3.   
  4.     private int mOriginalWindowAttachCount;  
  5.   
  6.     public void run() {  
  7.         if (isPressed() && (mParent != null)  
  8.                 && mOriginalWindowAttachCount == mWindowAttachCount) {  
  9.             if (performLongClick()) {  
  10.                 mHasPerformedLongPress = true;  
  11.             }  
  12.         }  
  13.     }  
  14.   
  15.     public void rememberWindowAttachCount() {  
  16.         mOriginalWindowAttachCount = mWindowAttachCount;  
  17.     }  
  18. }  


 ====代码七.一====

  上面已经说过,在115ms以内,已经获得了用户在DOWN事件之后的下一个动作行为,并且到时间到达115ms的时候,CheckForLongPress的线程也已经被创建了。从代码五中我们可以看到,DOWN事件中post(...)方法执行后,代码会接着往下走,且返回值为true。DOWN事件被onTouchEvent消化掉了。接下来,就分两种情况讨论接下来的代码流程。一为用户松手了,二为用户还按着没动。

  PS:通过上面的代码,我们其实可以知道,要处理一个LongClick事件,是要等待500ms的。115ms用来检测下一个动作,再过345ms是为了等待达到长按处理的条件。

  情况一:用户触碰完屏幕后马上松开(用户松手了)

  在这种情况下,由于DOWN事件已经处理完毕,UP事件将被执行。

  1、DragLayer.java的onInterceptTouchEvent()方法处理UP事件

  代码请参见代码一。startDrag()方法并未被执行,因此mShouldDrop为false,程序将执行endDrag()方法。它主要是做的工作是重复使用拖拽图片的资源,将当前拖拽的图片设置为可见以及通知所有的DragListener的实现类,拖拽动作已经结束。其代码如下:

[java] view plain copy
  1. ////代码八  
  2. private void endDrag() {  
  3.     if (mDragging) {  
  4.         mDragging = false;  
  5.         if (mDragBitmap != null) {  
  6.             mDragBitmap.recycle();  
  7.         }  
  8.         if (mOriginator != null) {  
  9.             mOriginator.setVisibility(VISIBLE);  
  10.         }  
  11.         // Faruq: ArrayList For Each  
  12.         for (DragListener l : mListener) {  
  13.             l.onDragEnd();  
  14.         }    
  15.     }  
  16. }  


 ====代码八====

mDragging为false,endDrag()没给我们做什么事。DragLayer不拦截UP事件,UP事件往下传递。

  2、Workspace.java的onInterceptTouchEvent()方法处理UP事件

  Workspace.java的onInterceptTouchEvent()方法中UP事件部分的代码如下:

[java] view plain copy
  1. ////代码九  
  2. @Override  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  4.     final float x = ev.getX();  
  5.     final float y = ev.getY();  
  6.   
  7.     switch (action) {  
  8.         case MotionEvent.ACTION_MOVE:  
  9.             //...  
  10.             break;  
  11.   
  12.         case MotionEvent.ACTION_DOWN:  
  13.             //...  
  14.             break;  
  15.   
  16.         case MotionEvent.ACTION_CANCEL:  
  17.         case MotionEvent.ACTION_UP:  
  18.             if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_SWIPE_DOWN_GESTURE && mTouchState != TOUCH_SWIPE_UP_GESTURE) {  
  19.                 final CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);  
  20.                 if (!currentScreen.lastDownOnOccupiedCell()) {  
  21.                 //得到当前view在屏幕上的坐标  
  22.                     getLocationOnScreen(mTempCell);  
  23.                     // Send a tap to the wallpaper if the last down was on empty space  
  24.                     //重新设置桌面壁纸  
  25.                     if(lwpSupport)  
  26.                     mWallpaperManager.sendWallpaperCommand(getWindowToken(),  
  27.                             "android.wallpaper.tap",  
  28.                             mTempCell[0] + (int) ev.getX(),  
  29.                             mTempCell[1] + (int) ev.getY(), 0null);  
  30.                 }  
  31.             }  
  32.             // Release the drag  
  33.             clearChildrenCache();  
  34.             mTouchState = TOUCH_STATE_REST;  
  35.             //longPress将不会被执行  
  36.             mAllowLongPress = false;  
  37.             break;  
  38.     }  
  39.     /* 
  40.      * The only time we want to intercept motion events is if we are in the 
  41.      * drag mode. 
  42.      * 仅仅在拖动模式下才拦截事件 
  43.      */  
  44.     return mTouchState != TOUCH_STATE_REST;  
  45. }  


 ====代码九====

其中三个if条件都成立,currentScreen.lastDownOnOccupiedCell()得到的是我们前面的found值,此时为false。该段代码的作用就是重新设置壁纸位置。此处lwpSupport的值为false。

代码最终执行返回false,UP事件接着往下传递。

  3、CellLayout.java的onInterceptTouchEvent()方法处理UP事件

  代码参见代码三,UP事件的处理中只做了一件事,将CellInfo的信息清空。此时返回false。事件不被拦截,而且CellLayout为最底层的view,因此执行它的onTouchEvent()方法。onTouchEvent()方法CellLayout并没有重写,这个时候又要重新回到View.java中执行onTouchEvent()方法了。CellLayout.java中onInterceptTouchEvent()方法的UP事件的代码如下:

[java] view plain copy
  1. ////代码十  
  2.  public boolean onTouchEvent(MotionEvent event) {  
  3.     final int viewFlags = mViewFlags;  
  4.     //....  
  5.     //判断当前控件是否可点击,是否可长按  
  6.     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  7.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  8.         switch (event.getAction()) {  
  9.             case MotionEvent.ACTION_UP:  
  10.                  boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  11.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  12.                     // take focus if we don't have it already and we should in  
  13.                     // touch mode.  
  14.                     boolean focusTaken = false;  
  15.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  16.                         focusTaken = requestFocus();  
  17.                     }  
  18.   
  19.                     if (!mHasPerformedLongPress) {  
  20.                         // This is a tap, so remove the longpress check  
  21.                         //移除执行长按事件的线程  
  22.                         removeLongPressCallback();  
  23.   
  24.                         // Only perform take click actions if we were in the pressed state  
  25.                         if (!focusTaken) {  
  26.                             // Use a Runnable and post this rather than calling  
  27.                             // performClick directly. This lets other visual state  
  28.                             // of the view update before click actions start.  
  29.                             if (mPerformClick == null) {  
  30.                                 mPerformClick = new PerformClick();  
  31.                             }  
  32.                             if (!post(mPerformClick)) {  
  33.                                 performClick();  
  34.                             }  
  35.                         }  
  36.                     }  
  37.   
  38.                     if (mUnsetPressedState == null) {  
  39.                         mUnsetPressedState = new UnsetPressedState();  
  40.                     }  
  41.   
  42.                     if (prepressed) {  
  43.                         mPrivateFlags |= PRESSED;  
  44.                         refreshDrawableState();  
  45.                         postDelayed(mUnsetPressedState,  
  46.                                 ViewConfiguration.getPressedStateDuration());  
  47.                     } else if (!post(mUnsetPressedState)) {  
  48.                         // If the post failed, unpress right now  
  49.                         mUnsetPressedState.run();  
  50.                     }  
  51.                     //移除等待用户下一个动作的线程  
  52.                     removeTapCallback();  
  53.                 }  
  54.                 break;  
  55.   
  56.             case MotionEvent.ACTION_DOWN:  
  57.                 //...  
  58.                 break;  
  59.   
  60.             case MotionEvent.ACTION_CANCEL:  
  61.                 //...  
  62.                 break;  
  63.   
  64.             case MotionEvent.ACTION_MOVE:  
  65.                 //...  
  66.                 break;  
  67.         }  
  68.         return true;  
  69.     }  
  70.     return false;  
  71. }  


 ====代码十====

mHasPerformedLongPress此时为默认值false。上面这段代码主要做三件事,1、将CheckForTap线程从消息队列(Message Queue)中移除(removeTapCallback()实现),2、将CheckForLongPress线程从消息队列中移除(removeLongPressCallback()方法实现,这里程序很可能已经过去了115ms,所以要进行移除长按等待线程的操作),3、执行点击操作。

执行点击操作的具体过程是:

先使用post(mPerformClick)将PerformClick(执行performClick())发送到主线程中并立即执行,如果执行成功,跳过if语句中的内容,如果执行失败,再调用一次 performClick()方法。 performClick()的代码如下,它就是回调当前OnClickListener类的onClick()回调方法。

[java] view plain copy
  1. ////代码十一  
  2. /** 
  3.  * Call this view's OnClickListener, if it is defined. 
  4.  * 
  5.  * @return True there was an assigned OnClickListener that was called, false 
  6.  *         otherwise is returned. 
  7.  */  
  8. public boolean performClick() {  
  9.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  10.   
  11.     if (mOnClickListener != null) {  
  12.         playSoundEffect(SoundEffectConstants.CLICK);  
  13.         mOnClickListener.onClick(this);  
  14.         return true;  
  15.     }  
  16.   
  17.     return false;  
  18. }  

 ====代码十一====

如果你对当前view设置了setOnClickListener的话,对应给的onClick()方法就要执行了。但是这里,我们的CellLayout并没有被设置setOnClickListener,也就说此处mOnClickListener为null,没有可供回调的方法,因此用户点击空白处的操作到此就全部结束了。PS:童鞋们可以自己去Launcher.java中找到这个方法:createShortcut()。在这个方法中,桌面元素是会被设置setOnClickListener的。这也就是点击桌面元素,桌面元素会被打开的原因了。因为在这一步之前,他们的操作过程基本是相同的。只不过,点击桌面元素多了一步BubbleTextView来处理点击事件的过程(其实你进去BubbleTextView中看,实际上它也什么都没干)。

  情况二:用户触碰完屏幕后按着没动

  这种情况下,我们认为用户没动(按着没动),此时程序就会卡在这里等着了,等待500ms,让程序等待CheckForTap和CheckForLongPress两个线程的执行。当CheckForLongPress线程被顺利执行以后,我们就可以接着往下看了。

  从代码七.一中我们可以看到,performLongClick()将要去处理长按事件

  performLongClick()方法的代码如下:

[java] view plain copy
  1. ////代码十二  
  2.  /** 
  3.  * Launches the intent referred by the clicked shortcut. 
  4.  * 
  5.  * @param v The view representing the clicked shortcut. 
  6.  */  
  7. public void onClick(View v) {  
  8.     Object tag = v.getTag();  
  9.     //ADW: Check if the tag is a special action (the app drawer category navigation)  
  10.     if(tag instanceof Integer){  
  11.         navigateCatalogs(Integer.parseInt(tag.toString()));  
  12.         return;  
  13.     }  
  14.     //TODO:ADW Check whether to display a toast if clicked mLAB or mRAB withount binding  
  15.     //ActionButton还未加载出来的时候点击处理  
  16.     if(tag instanceof ItemInfo && tag==null && v instanceof ActionButton){  
  17.         Toast t=Toast.makeText(this, R.string.toast_no_application_def, Toast.LENGTH_SHORT);  
  18.         t.show();  
  19.         return;  
  20.     }  
  21.     //应用程序点击操作,包括ActionButton  
  22.     if (tag instanceof ApplicationInfo) {  
  23.         // Open shortcut  
  24.         final ApplicationInfo info=(ApplicationInfo) tag;  
  25.         final Intent intent = info.intent;  
  26.         int[] pos = new int[2];  
  27.         v.getLocationOnScreen(pos);  
  28.         try{  
  29.         intent.setSourceBounds(  
  30.                 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));  
  31.         }catch(NoSuchMethodError e){};  
  32.         startActivitySafely(intent);  
  33.         //Close dockbar if setting says so  
  34.         if(info.container==LauncherSettings.Favorites.CONTAINER_DOCKBAR && isDockBarOpen() && autoCloseDockbar){  
  35.             mDockBar.close();  
  36.         }  
  37.     }  
  38.     //文件夹点击操作  
  39.     else if (tag instanceof FolderInfo) {  
  40.         handleFolderClick((FolderInfo) tag);  
  41.     }  
  42. }  
  43.   
  44. /** 
  45.  * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the 
  46.  * OnLongClickListener did not consume the event. 
  47.  * 
  48.  * @return True if one of the above receivers consumed the event, false otherwise. 
  49.  */  
  50. public boolean performLongClick() {  
  51.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  
  52.   
  53.     boolean handled = false;  
  54.     if (mOnLongClickListener != null) {  
  55.         handled = mOnLongClickListener.onLongClick(View.this);  
  56.     }  
  57.     if (!handled) {  
  58.         handled = showContextMenu();  
  59.     }  
  60.     if (handled) {  
  61.     //触碰感应  
  62.         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);  
  63.     }  
  64.     return handled;  
  65. }  


  ====代码十二====

  代码很简单,取得mOnLongClickListener对象,执行它的onLongClick()方法。还记得此时的onLongClick的调用链是从何处开始的吗?别忘了,是从CellLayout过来的。意思就是说:CellLayout.java给LongClick()设置监听了。我们就来看一看。

找到Launcher.java中的这段代码:

workspace.setOnLongClickListener(this);

它调用了Workspace.java的setOnLongClick()方法,其代码如下:

[java] view plain copy
  1. ////代码十三  
  2. /** 
  3.  * Registers the specified listener on each screen contained in this workspace. 
  4.  * 
  5.  * @param l The listener used to respond to long clicks. 
  6.  */  
  7. @Override  
  8. public void setOnLongClickListener(OnLongClickListener l) {  
  9.     mLongClickListener = l;  
  10.     final int count = getChildCount();  
  11.     for (int i = 0; i < count; i++) {  
  12.         getChildAt(i).setOnLongClickListener(l);  
  13.     }  
  14. }  

====代码十三====

getChildAt(i)得到Workspace的子控件,然后给它设置onLongClick(l),并且这个l是来自Launcher的,也就是Launcher.java。Workspace的子控件不就是CellLayout吗?现在关系理清了,CellLayout确实被设置了长按点击事件。

情况二的结尾部分就是到Launcher.java中执行OnLongClick()方法了。这个就大家自己去看吧。

   Step5:用户触碰桌面元素图标

用户触碰的不是空白处,而是桌面元素图标,也要分两种情况讨论。

情况一:用户单击桌面元素

  如果用户是单击,那就没什么好讲的吧。跟Step4中的情况是一样的,只不过是从BubbleTextView中调用super.onTouchEvent()的。而且与Step4中情况一中不同的是:点击桌面元素的时候,将会去回调桌面元素图标的方法,它的回调方法是在该处设置的,Launcher.java的这段代码:

favorite.setOnClickListener(this);

情况二:用户长按桌面元素

  这种情况跟长按桌面空白处也差不多,唯一不同的是,如果用户长按的地方是桌面元素的区域,此时CellInfo.cell就不为空了(见代码三处的分析)。根据Step4中情况二的分析,用户长按桌面元素之后也要执行Launcher.java的onLongClick()方法。PS:桌面元素的长按事件是在此处添加的,Workspace.java的addInScreen()方法中。这个方法是ADW_Launcher中一个非常重要的方法,正是该方法将所有的桌面元素加入到CellLayout中的,然后CellLayout的onLayout会被调用来完成桌面元素的展示。(这一部分内容关系到ADW_Launcher的启动,要对onMeasure和onLayout过程了解。ADW_Launcher的启动,以后有时间分析。)addInScreen()方法的代码如下:

[java] view plain copy
  1. ////代码十四  
  2. /** 
  3.  * Adds the specified child in the specified screen. The position and dimension of 
  4.  * the child are defined by x, y, spanX and spanY. 
  5.  * 
  6.  * @param child The child to add in one of the workspace's screens. 
  7.  * @param screen The screen in which to add the child. 
  8.  * @param x The X position of the child in the screen's grid. 
  9.  * @param y The Y position of the child in the screen's grid. 
  10.  * @param spanX The number of cells spanned horizontally by the child. 
  11.  * @param spanY The number of cells spanned vertically by the child. 
  12.  * @param insert When true, the child is inserted at the beginning of the children list. 
  13.  */  
  14. void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {  
  15.     if (screen < 0 || screen >= getChildCount()) {  
  16.         /* Rogro82@xda Extended : Do not throw an exception else it will crash when there is an item on a hidden homescreen */  
  17.         return;  
  18.         //throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());  
  19.     }  
  20.     //ADW: we cannot accept an item from a position greater that current desktop columns/rows  
  21.     if(x>=mDesktopColumns || y>=mDesktopRows){  
  22.         return;  
  23.     }  
  24.     clearVacantCache();  
  25.   
  26.     final CellLayout group = (CellLayout) getChildAt(screen);  
  27.     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();  
  28.     if (lp == null) {  
  29.         lp = new CellLayout.LayoutParams(x, y, spanX, spanY);  
  30.     } else {  
  31.         lp.cellX = x;  
  32.         lp.cellY = y;  
  33.         lp.cellHSpan = spanX;  
  34.         lp.cellVSpan = spanY;  
  35.     }  
  36.     group.addView(child, insert ? 0 : -1, lp);  
  37.     if (!(child instanceof Folder)) {  
  38.     //长按事件在此处设置,mLongClickListener为Launcher.java  
  39.         child.setOnLongClickListener(mLongClickListener);  
  40.     }  
  41. }  

 ====代码十四====

  我们接着往下走,到Launcher.java的onLongClick()方法中去看一看,其部分代码如下:

[java] view plain copy
  1. ////代码十五  
  2. public boolean onLongClick(View v) {  
  3.     //....  
  4.     CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();  
  5.     // This happens when long clicking an item with the dpad/trackball  
  6.     if (cellInfo == null) {  
  7.         return true;  
  8.     }  
  9.     if (mWorkspace.allowLongPress()) {  
  10.         if (cellInfo.cell == null) {  
  11.             if (cellInfo.valid) {  
  12.                 // User long pressed on empty space  
  13.                 mWorkspace.setAllowLongPress(false);  
  14.                 showAddDialog(cellInfo);  
  15.             }  
  16.         } else {  
  17.             if (!(cellInfo.cell instanceof Folder)) {  
  18.                 // User long pressed on an item  
  19.                 //长按桌面元素图标  
  20.                 mWorkspace.startDrag(cellInfo);  
  21.             }  
  22.         }  
  23.     }  
  24.     //还得注意这里,return true的话,onClick是无法再执行了的。  
  25.     //onLongClick在DOWN之后,UP之前执行  
  26.     //onClick在UP之后执行。  
  27.     //具体原因是如果你的onLongClick返回true,mHasPerformedLongPress会被置为true,而onClick的执行是要在mHasPerformedLongPress为false的情况下。  
  28.     //参见代码七.一和代码十  
  29.     return true;  
  30. }  

 ====代码十五====

  onLongClick()将去执行Workspace.java的startDrag()方法,并且将CellInfo传递进去。Workspace.java中的startDrag()方法如下:

[java] view plain copy
  1. ////代码十六  
  2.  void startDrag(CellLayout.CellInfo cellInfo) {  
  3.     View child = cellInfo.cell;  
  4.   
  5.     // Make sure the drag was started by a long press as opposed to a long click.  
  6.     // Note that Search takes focus when clicked rather than entering touch mode  
  7.     if (!child.isInTouchMode() && !(child instanceof Search)) {  
  8.         return;  
  9.     }  
  10.       
  11.     mDragInfo = cellInfo;  
  12.     mDragInfo.screen = mCurrentScreen;  
  13.   
  14.     CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));  
  15.   
  16.     current.onDragChild(child);  
  17.     mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);  
  18.     invalidate();  
  19.     clearVacantCache();  
  20. }  

 ====代码十六====

  它会接着调用DragLayer.java的startDrag()方法,传递的四个参数,child表示当前拖动的view,this表示拖动来自当前(Workspace)这个拖动源,实现了DragSource的类我们可以称之为拖动源,比如MiniLauncher也实现了该接口,用来标识它是一个可以作为拖动来源容器的类。关于DragLayer.java的startDrag()方法,其实我们在代码一的下面就有提到。startDrag()设置了几个很重要的值:包括正在拖动的元素是什么类型的View,应用程序还是文件夹等等;正在拖动的元素来自于哪个容器,DragSource;正在拖动的元素的信息;以及正在拖动元素的大小变化和长按触碰反应(振动一下)也是在此处实现的。

  到这里,我们可以看到的现象是:桌面元素变大了(一般会是这样),并且为选中状态。我们表面上看不到的呢?很多重要的变量被设置了相关的值,这个上面有提到。现在我们接着往下走。

  直到DragLayer.java的startDrag()方法执行完毕,长按桌面元素的DOWN行为就全部执行完毕了,接下来就是MOVE事件的操作了(至于用户不拖动而UP的情况,事件会被DragLayer拦截,然后释放相应的资源,这里就不多说了)。

  Step5.1:DragLayer.java中onInterceptTouchEvent()方法对MOVE事件的处理

通过代码一我们知道,onInterceptTouchEvent()中并未对move做任何的处理,但是由于我们执行了startDrag()方法,此时mDragging被设置成了true。也就说,此时DragLayer.java将拦截MOVE事件,于是Workspace.java和CellLayout.java中的onInterceptTouchEvent()方法将执行CANCLE操作。当DragLayer.java将MOVE事件拦截下来之后,后续所有的MOVE事件将交给DragLayer.java的onTouchEvent()来处理了。onTouchEvent()中MOVE事件的操作可就复杂了。其中的findDropTarget()是一个很重要的方法,它的作用是找到当前元素正在哪个可放置拖动元素容器(DropTarget)的上方,,程序会根据这个来改变该DropTarget的一些状态。当用户松手的时候,UP被执行,在drop()方法中DropTarget的onDrop()方法将被执行。这里就不分析代码了,童鞋们可以自己去看源代码。

Step6:用户滑动屏幕处理流程

  Step4中的情况二中,我们已经说到,只有等待了500ms并且还没接收到UP或者MOVE行为的时候,就执行了长按事件。那么当用户在500ms以内(处理桌面元素和桌面空白处的长按事件之前),系统接收到了用户的MOVE行为的话,此时就是滑屏操作了。

  滑屏操作也是要分条件的。如果滑动的距离较小的话,此时move事件会被传给CellLayout或者BubbleTextView的onTouchEvent()方法处理,但是他们都没有重写该方法,所以归根到底处理其实就是发生在Workspace的onInterceptTouchEvent中的(指的是屏幕的滑动,)。如果用户的滑动距离到达了某一个值的时候,此时Workspace就要拦截下事件处理了。因为只有达到了这样的条件,才能实现滑屏。

  在DragLayer.java中,由于没有执行startDrag()方法,因此DragLayer不拦截事件,MOVE事件被传递到Workspace.java的onInterceptTouchEvent()方法中,其MOVE部分的代码如下:

[java] view plain copy
  1. ////代码十七  
  2. @Override  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  4.     //....  
  5.     final float x = ev.getX();  
  6.     final float y = ev.getY();  
  7.   
  8.     switch (action) {  
  9.         case MotionEvent.ACTION_MOVE:  
  10.             /* 
  11.              * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
  12.              * whether the user has moved far enough from his original down touch. 
  13.              */  
  14.   
  15.             /* 
  16.              * Locally do absolute value. mLastMotionX is set to the y value 
  17.              * of the down event. 
  18.              */  
  19.             final int xDiff = (int) Math.abs(x - mLastMotionX);  
  20.             final int yDiff = (int) Math.abs(y - mLastMotionY);  
  21.   
  22.             final int touchSlop = mTouchSlop;  
  23.             boolean xMoved = xDiff > touchSlop;  
  24.             boolean yMoved = yDiff > touchSlop;  
  25.             //在x轴和y轴方向移动的距离是否大于某一个值  
  26.             if (xMoved || yMoved) {  
  27.                 // If xDiff > yDiff means the finger path pitch is smaller than 45deg so we assume the user want to scroll X axis  
  28.                 //此时mTouchState != TOUCH_STATE_REST;为true。workspace将拦截MOVE事件了。  
  29.                 if (xDiff > yDiff) {  
  30.                     // Scroll if the user moved far enough along the X axis  
  31.                     mTouchState = TOUCH_STATE_SCROLLING;  
  32.                     enableChildrenCache();  
  33.                 }  
  34.                 // If yDiff > xDiff means the finger path pitch is bigger than 45deg so we assume the user want to either scroll Y or Y-axis gesture  
  35.                 else if (getOpenFolder()==null)  
  36.                 {  
  37.                     //...  
  38.                 }  
  39.                 // Either way, cancel any pending longpress  
  40.                 if (mAllowLongPress) {  
  41.                     mAllowLongPress = false;  
  42.                     // Try canceling the long press. It could also have been scheduled  
  43.                     // by a distant descendant, so use the mAllowLongPress flag to block  
  44.                     // everything  
  45.                     final View currentScreen = getChildAt(mCurrentScreen);  
  46.                     currentScreen.cancelLongPress();  
  47.                 }  
  48.             }  
  49.             break;  
  50.   
  51.         case MotionEvent.ACTION_DOWN:  
  52.             //...  
  53.             break;  
  54.   
  55.         case MotionEvent.ACTION_CANCEL:  
  56.         case MotionEvent.ACTION_UP:  
  57.             //...  
  58.             break;  
  59.     }  
  60.     return mTouchState != TOUCH_STATE_REST;  
  61. }  

 ====代码十七====

  通过上面的代码我们可以看到,只有当x轴移动的方向或者y轴移动的方向大于某一个值时,MOVE事件才可能会被Workspace.java拦截。

  如果MOVE事件没有被Workspace.java拦截,MOVE被传递给CellLayout.java,它的onInterceptTouchEvent()方法中并没有对MOVE事件做处理,MOVE事件就会被CellLayout或者BubbleTextView消化掉,然后从DragLayer.java开始执行UP事件,这一过程我们在前面就分析过了,屏幕也不会有任何的反应。

  如果MOVE事件被Workspace.java拦截了,此时就由Workspace.java的onTouchEvent()方法来了处理后续事件了。我们看看Workspace.java中的onTouchEvent()方法,如果曾经做过引导页滑动效果的童鞋,对这段一定会倍感亲切的。其代码如下:

[java] view plain copy
  1. ////代码十八  
  2. @Override  
  3.     public boolean onTouchEvent(MotionEvent ev) {  
  4.         //...  
  5.   
  6.         final int action = ev.getAction();  
  7.         final float x = ev.getX();  
  8.   
  9.         switch (action) {  
  10.         case MotionEvent.ACTION_DOWN:  
  11.             //....  
  12.             break;  
  13.         case MotionEvent.ACTION_MOVE:  
  14.             System.out.println(LOG_TAG + "onTouchEvent----ACTION_MOVE");  
  15.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  16.                 // Scroll to follow the motion event  
  17.                 final int deltaX = (int) (mLastMotionX - x);  
  18.                 mLastMotionX = x;  
  19.   
  20.                 if (deltaX < 0) {  
  21.                     if (mScrollX > -mScrollingBounce) {  
  22.                     //当前屏幕滚动多少距离  
  23.                         scrollBy(Math.min(deltaX,mScrollingBounce), 0);  
  24.                         if(lwpSupport)updateWallpaperOffset();  
  25.                         if(mLauncher.getDesktopIndicator()!=null)mLauncher.getDesktopIndicator().indicate((float)getScrollX()/(float)(getChildCount()*getWidth()));  
  26.                     }  
  27.                 } else if (deltaX > 0) {  
  28.                     final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -  
  29.                             mScrollX - getWidth()+mScrollingBounce;  
  30.                     if (availableToScroll > 0) {  
  31.                         scrollBy(deltaX, 0);  
  32.                         if(lwpSupport)updateWallpaperOffset();  
  33.                         if(mLauncher.getDesktopIndicator()!=null)mLauncher.getDesktopIndicator().indicate((float)getScrollX()/(float)(getChildCount()*getWidth()));  
  34.                     }  
  35.                 }  
  36.             }  
  37.             break;  
  38.         case MotionEvent.ACTION_UP:  
  39.             System.out.println(LOG_TAG + "onTouchEvent----ACTION_UP");  
  40.             if (mTouchState == TOUCH_STATE_SCROLLING) {  
  41.                 final VelocityTracker velocityTracker = mVelocityTracker;  
  42.                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  43.                 int velocityX = (int) velocityTracker.getXVelocity();  
  44.   
  45.                 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {  
  46.                     // Fling hard enough to move left  
  47.                     //速度足够大时,直接滚动到某一屏  
  48.                     snapToScreen(mCurrentScreen - 1);  
  49.                 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {  
  50.                     // Fling hard enough to move right  
  51.                     snapToScreen(mCurrentScreen + 1);  
  52.                 } else {  
  53.                 //距离足够大时,使用这种方式滚动  
  54.                     snapToDestination();  
  55.                 }  
  56.   
  57.                 if (mVelocityTracker != null) {  
  58.                     mVelocityTracker.recycle();  
  59.                     mVelocityTracker = null;  
  60.                 }  
  61.             } else if (mTouchState == TOUCH_SWIPE_DOWN_GESTURE )  
  62.             {  
  63.                 mLauncher.fireSwipeDownAction();  
  64.             } else if (mTouchState == TOUCH_SWIPE_UP_GESTURE )  
  65.             {  
  66.                 mLauncher.fireSwipeUpAction();  
  67.             }  
  68.             mTouchState = TOUCH_STATE_REST;  
  69.             break;  
  70.         case MotionEvent.ACTION_CANCEL:  
  71.             mTouchState = TOUCH_STATE_REST;  
  72.         }  
  73.   
  74.         return true;  
  75.     }  

 ====代码十八====

  上面的scrollBy,snapToScreen,snapToDestination这几个方法是不是特别的熟悉,第一个表示滚动多少距离,第二个用来处理当滑动的速度大于某一个值时,滑动到某一屏,第三个方法用来处理当滑动的距离大于某一个值时,滑动到某一屏。这里具体的就不再多讲了,关于滑屏更具体的内容看下面的小DEMO吧。这个DEMO不是我写的,只是原来看到别人写的DEMO稍微的加了一点注释而已。

  至此,ADW_Launcher中屏幕的事件处理流程就已经全部分析完毕了。也不知道讲清楚了没有,逻辑比较混乱,稍微的总结一下:

1、用户点击屏幕空白处,DOWN,UP事件最终都被CellLayout.java消耗,随后View.java中的onTouchEvent()方法将去执行,根据当前View是否可以Clickable和是否设置了OnClickListener来决定是否有可以处理的onClick()。由于CellLayout.java没有设置onClick()监听,因此此时我们看到的效果是屏幕没有反应。

2、用户长按屏幕空白处,DOWN事件被CellLayout.java消耗,随后View.java中的onTouchEvent()中的DOWN被执行,长按后当前view的onLongClick()回调方法会被执行,而当前的View为CellLayout,OnLongClickListener为Launcher.java。于是Launcher.java中的onLongClick()会被执行。更具体的见代码十二下面的分析。接下去UP事件被View.java消耗。最后我们看到的效果就是Launcher.java中onLongClick()中showAddDialog()方法被执行之后的效果。

3、用户点击桌面元素,流程和1中的相同,只不过桌面元素都被设置了点击事件,因此会被执行Launcher.java中的onClick()方法和对应的点击方法(比如ActionButton也设置了点击回调)。关于点击是如何设置的,请看step5的分析。最后用户看到的效果就是桌面元素运行。

4、用户长按桌面元素,BubbleTextView.java的onTouchEvent()消耗DOWN事件,接着Launcher.java中的OnLongClick()方法被执行,代码mDragger.startDrag()被执行,设置一些很重要的参数。接着MOVE事件被DragLayer.java拦截,然后DragLayer.java的onTouchEvent()处理接下来的MOVE和UP事件。最后看到的效果是桌面元素可以在整个DragLayer层上*移动。

5、用户滑动屏幕,DOWN事件被CellLayout或者BubbleTextView消耗,然后MOVE事件会Workspace.java拦截(可能会被传递到CellLayout或者BubbleTextView中),接下去后续的MOVE事件被Workspace.java的onTouchEvent()方法处理,完成屏幕的滑动操作。

  我们知道,ADW_Launcher的最顶层是DragLayer,而桌面元素(一般是应用程序或者快捷方式)是可以在整个DragLayer层滑动的,包括删除框,MiniLauncher和Workspace上,因此ADW_Launcher中元素滑动的操作交给了DragLayer来处理。而屏幕的滑动是发生在Workspace层上的,因此ADW_Launcher中屏幕滑动的操作就交给了Workspace来处理。

  上面对ADW_Launcher中事件的处理做了一定的总结,也仅仅是个人的一点见解,有不对的地方望指正,欢迎讨论。最后,给出的一个DEMO就是屏幕滑动的demo,在做引导页的时候一般都会用到。Demo来自网友,非本人原创,只不过在有些地方加了部分注解,帮助理解。