码迷,mamicode.com
首页 > 移动开发 > 详细

Android4.0 Launcher 源码分析3——WorkSpace结构

时间:2016-05-10 18:10:19      阅读:290      评论:0      收藏:0      [点我收藏+]

标签:

3、WorkSpace结构 

技术分享

  桌面的左右滑动功能主要是在PagedView类中实现的,而WorkSpace是PagedView类的子类,所以会继承PagedView中的方法。当我们的手指点击WorkSpace时,首先就会触发PageView中的onInterceptTouchEvent()方法,会根据相应的条件来判断是否对Touch事件进行拦截,如果onInterceptTouchEvent()方法返回为true,则会对Touch事件进行拦截,PageView类的onTouch方法会进行响应从而得到调用。如果返回false,就分两钟情况:

  1. 我们是点击在它的子控键上进行滑动时,比如我们是点击在桌面的图标上进行左右滑动的,workspace则会把Touch事件分发给它的子控件。
  2. 而如果仅仅是点击到桌面的空白出Touch 事件就不会发生响应。

WorkSpace滑动事件:

  1. MotionEvent.ACTION_DOWN:在我们手指第一次触摸到屏幕时,首先会对onInterceptTouchEvent中的事件进行判断,如果是按下事件 (MotionEvent.ACTION_DOWN),则会记录按下时的X坐标、Y坐标等等数据,同时改变现在Workspace的状态为滚动状态(TOUCH_STATE_SCROLLING),这时会返回 ture,把事件交给onTouchEvent函数来处理,onTouchEvent中同样会对事件类型进行判断,当事件方法为 (MotionEvent.ACTION_DOWN)的时候,就可以开始显示滚动的指示条了(就是Hotseat上PageIndirector显示第几屏的屏点)。
  2. MotionEvent.ACTION_MOVE:当我们按着屏幕不放进行滑动的时候,又会在onInterceptTouchEvent进行事件拦截,但是现在的事件类型变为了MotionEvent.ACTION_MOVE,因为是移动的操作,所以会在拦截的时候取消桌面长按的事件的响应,同时转到onTouchEvent中对ACTION_MOVE事件的响应中,判断我们移动了多少距离,使用scrollBy方法来对桌面进行移动,并刷新屏幕。
  3. MotionEvent.ACTION_UP:最后我们放开手后会触发 onTouchEvent中的MotionEvent.ACTION_UP事件,这时会根据滑动的情况来判断是朝左滑动还是朝右滑动,如果手指只滑动了屏 幕宽度的少一半距离,则会弹回原来的页面,滑动多于屏幕宽度的一半则会进行翻页。

  同时要注意无论在什么情况下触发了WorkSpace滑动的事件,则系统会不断调用computeScroll()方法,我们重写这个方法同时在这个方法中调用刷新界面等操作。

  滑动过程中所要注意的主要方法如下,具体见代码注释。

  1     //对Touch事件进行拦截   主要用于在拦截各种Touch事件时,设置mTouchState的各种状态 
  2     @Override 
  3     public boolean onInterceptTouchEvent(MotionEvent ev) { 
  4         /* 
  5          * This method JUST determines whether we want to intercept the motion. 
  6          * If we return true, onTouchEvent will be called and we do the actual 
  7          * scrolling there. 
  8          * 这个方法仅仅决定了我们是否愿意去对滑动事件进行拦截,如果返回为true,则会调用onTouchEvent我们将会在那里进行事件处理 
  9          */ 
 10         //对滑动的速率进行跟踪。 
 11      
 12         acquireVelocityTrackerAndAddMovement(ev); 
 13      
 14         // Skip touch handling if there are no pages to swipe 
 15         // 如果没有页面,则跳过操作。 
 16         if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); 
 17      
 18         /* 
 19          * Shortcut the most recurring case: the user is in the dragging 
 20          * state and he is moving his finger.  We want to intercept this 
 21          * motion. 
 22          * shortcut最常见的情况是:用户处于拖动的状态下,同时在移动它的手指,这时候我们需要拦截这个动作。 
 23          *  
 24          */ 
 25         final int action = ev.getAction(); 
 26         //如果是在MOVE的情况下,则进行Touch事件拦截 
 27         if ((action == MotionEvent.ACTION_MOVE) &&               
 28                 (mTouchState == TOUCH_STATE_SCROLLING)) { 
 29             return true; 
 30         } 
 31      
 32         switch (action & MotionEvent.ACTION_MASK) { 
 33             case MotionEvent.ACTION_MOVE: { 
 34                 /* 
 35                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
 36                  * whether the user has moved far enough from his original down touch. 
 37                  * 如果mIsBeingDragged==false ,否则快捷方式应该捕获到该事件,检查一下用户从它点击的地方位移是否足够 
 38                  */ 
 39                 if (mActivePointerId != INVALID_POINTER) { 
 40                     //根据移动的距离判断是翻页还是移动一段位移,同时设置lastMotionX或者mTouchState这些值。同时取消桌面长按事件。 
 41                     determineScrollingStart(ev); 
 42                     break; 
 43                 } 
 44                 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN 
 45                 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN 
 46                 // i.e. fall through to the next case (don‘t break) 
 47                 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events 
 48                 // while it‘s small- this was causing a crash before we checked for INVALID_POINTER) 
 49                 // 如果mActivePointerId 是 INVALID_POINTER,这时候我们应该已经错过了ACTION_DOWN事件。在这种情况下,把 
 50                 // 第一次发生移动的事件当作ACTION——DOWN事件,直接进入下一个情况下。 
 51                 // 我们有时候会错过workspace中的ACTION_DOWN事件,因为在workspace变小的时候会忽略掉所有的事件。                 
 52             } 
 53      
 54             case MotionEvent.ACTION_DOWN: { 
 55                 final float x = ev.getX(); 
 56                 final float y = ev.getY(); 
 57                 // Remember location of down touch 
 58                 // 记录按下的位置 
 59                 mDownMotionX = x; 
 60                 mLastMotionX = x; 
 61                 mLastMotionY = y; 
 62                 mLastMotionXRemainder = 0; 
 63                 mTotalMotionX = 0; 
 64                 //Return the pointer identifier associated with a particular pointer data index is this event.  
 65                 //The identifier tells you the actual pointer number associated with the data,  
 66                 //accounting for individual pointers going up and down since the start of the current gesture. 
 67                 //返回和这个事件关联的触点数据id,计算单独点的id会上下浮动,因为手势的起始位置挥发声改变。 
 68                 mActivePointerId = ev.getPointerId(0); 
 69                 mAllowLongPress = true; 
 70      
 71                 /* 
 72                  * If being flinged and user touches the screen, initiate drag; 
 73                  * otherwise don‘t.  mScroller.isFinished should be false when 
 74                  * being flinged. 
 75                  * 如果被拖动同时用户触摸到了屏幕,就开始初始化拖动,否则便不会。 
 76                  * 当拖动完成后mScroller.isFinished就应该设置为false. 
 77                  *  
 78                  */ 
 79                 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); 
 80                  
 81                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); 
 82                 if (finishedScrolling) { 
 83                     //标记为TOUCH_STATE_REST状态 
 84                     mTouchState = TOUCH_STATE_REST; 
 85                     //取消滚动动画 
 86                     mScroller.abortAnimation(); 
 87                 } else { 
 88                     //状态为TOUCH_STATE_SCROLLING 
 89                     mTouchState = TOUCH_STATE_SCROLLING; 
 90                 } 
 91      
 92                 // check if this can be the beginning of a tap on the side of the pages 
 93                 // to scroll the current page 
 94                 // 检测此事件是不是开始于点击页面的边缘来对当前页面进行滚动。                 
 95                 if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { 
 96                     if (getChildCount() > 0) { 
 97                         //根据触点的点位来判断是否点击到上一页,从而更新相应的状态 
 98                         if (hitsPreviousPage(x, y)) { 
 99                             mTouchState = TOUCH_STATE_PREV_PAGE; 
100                         } else if (hitsNextPage(x, y)) { 
101                             mTouchState = TOUCH_STATE_NEXT_PAGE; 
102                         } 
103                     } 
104                 } 
105                 break; 
106             } 
107      
108             case MotionEvent.ACTION_UP: 
109             case MotionEvent.ACTION_CANCEL: 
110                 //触点不被相应时,所做的动作 
111                 mTouchState = TOUCH_STATE_REST; 
112                 mAllowLongPress = false; 
113                 mActivePointerId = INVALID_POINTER; 
114                 //释放速率跟踪 
115                 releaseVelocityTracker(); 
116                 break; 
117      
118             case MotionEvent.ACTION_POINTER_UP: 
119                 onSecondaryPointerUp(ev); 
120                 releaseVelocityTracker(); 
121                 break; 
122         } 
123      
124         /* 
125          * The only time we want to intercept motion events is if we are in the 
126          * drag mode. 
127          * 我们唯一会去对移动事件进行拦截的情况时我们在拖动模式下 
128          */ 
129         if(DEBUG) Log.d(TAG, "onInterceptTouchEvent "+(mTouchState != TOUCH_STATE_REST)); 
130         //只要是mTouchState的状态不为TOUCH_STATE_REST,那么就进行事件拦截 
131         return mTouchState != TOUCH_STATE_REST; 
132     } 

  onTouchEvent方法,详细见代码注释:

  1 @Override 
  2 public boolean onTouchEvent(MotionEvent ev) { 
  3     // Skip touch handling if there are no pages to swipe 
  4     // 如果没有子页面,就直接跳过 
  5     if (getChildCount() <= 0) return super.onTouchEvent(ev); 
  6  
  7     acquireVelocityTrackerAndAddMovement(ev); 
  8  
  9     final int action = ev.getAction(); 
 10  
 11     switch (action & MotionEvent.ACTION_MASK) { 
 12     case MotionEvent.ACTION_DOWN: 
 13         /* 
 14          * If being flinged and user touches, stop the fling. isFinished 
 15          * will be false if being flinged. 
 16          * 如果在滑动的过程中下用户又点击桌面,则取消滑动,从而响应当前的点击。 
 17          * 在滑动的isFinished将返回false. 
 18          */ 
 19         if (!mScroller.isFinished()) { 
 20             mScroller.abortAnimation(); 
 21         } 
 22  
 23         // Remember where the motion event started 
 24         mDownMotionX = mLastMotionX = ev.getX(); 
 25         mLastMotionXRemainder = 0; 
 26         mTotalMotionX = 0; 
 27         mActivePointerId = ev.getPointerId(0); 
 28         //主要用来显示滚动条,表明要开始滚动了,这里可以进行调整,滚动条时逐渐显示还是立刻显示。 
 29         if (mTouchState == TOUCH_STATE_SCROLLING) { 
 30             pageBeginMoving(); 
 31         } 
 32         break; 
 33  
 34     case MotionEvent.ACTION_MOVE: 
 35         if (mTouchState == TOUCH_STATE_SCROLLING) { 
 36             // Scroll to follow the motion event 
 37             final int pointerIndex = ev.findPointerIndex(mActivePointerId); 
 38             final float x = ev.getX(pointerIndex); 
 39             final float deltaX = mLastMotionX + mLastMotionXRemainder - x; 
 40             //总共移动的距离 
 41             mTotalMotionX += Math.abs(deltaX); 
 42  
 43             // Only scroll and update mLastMotionX if we have moved some discrete amount.  We 
 44             // keep the remainder because we are actually testing if we‘ve moved from the last 
 45             // scrolled position (which is discrete). 
 46             // 如果我们移动了一小段距离,我们则移动和更新mLastMotionX 。我们保存Remainder变量是因为会检测我们 
 47  
 48             //是否是从最后的滚动点位移动的。                
 49             if (Math.abs(deltaX) >= 1.0f) { 
 50                 mTouchX += deltaX; 
 51                 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 
 52                 if (!mDeferScrollUpdate) { 
 53                     scrollBy((int) deltaX, 0); 
 54                     if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); 
 55                 } else { 
 56                     invalidate(); 
 57                 } 
 58                 mLastMotionX = x; 
 59                 mLastMotionXRemainder = deltaX - (int) deltaX; 
 60             } else { 
 61             //Trigger the scrollbars to draw. When invoked this method starts an animation to fade the  
 62             //scrollbars out after a default delay. If a subclass provides animated scrolling,  
 63             //the start delay should equal the duration of the scrolling animation. 
 64             //触发scrollbar进行绘制。 使用这个方法来启动一个动画来使scrollbars经过一段时间淡出。如果子类提供了滚动的动画,则 
 65             //延迟的时间等于动画滚动的时间。 
 66                 awakenScrollBars(); 
 67             } 
 68         } else { 
 69             determineScrollingStart(ev); 
 70         } 
 71         break; 
 72  
 73     case MotionEvent.ACTION_UP: 
 74         if (mTouchState == TOUCH_STATE_SCROLLING) { 
 75             final int activePointerId = mActivePointerId; 
 76             final int pointerIndex = ev.findPointerIndex(activePointerId); 
 77             final float x = ev.getX(pointerIndex); 
 78             final VelocityTracker velocityTracker = mVelocityTracker; 
 79             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 
 80             int velocityX = (int) velocityTracker.getXVelocity(activePointerId); 
 81             final int deltaX = (int) (x - mDownMotionX); 
 82             final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); 
 83             // 屏幕的宽度*0.4f 
 84             boolean isSignificantMove = Math.abs(deltaX) > pageWidth * 
 85                     SIGNIFICANT_MOVE_THRESHOLD; 
 86             final int snapVelocity = mSnapVelocity; 
 87  
 88             mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); 
 89  
 90             boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && 
 91                     Math.abs(velocityX) > snapVelocity; 
 92  
 93             // In the case that the page is moved far to one direction and then is flung 
 94             // in the opposite direction, we use a threshold to determine whether we should 
 95             // just return to the starting page, or if we should skip one further. 
 96             // 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。我们使用一个阀值来判断是进行翻页还是返回到初始页面         
 97             boolean returnToOriginalPage = false; 
 98             if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && 
 99                     Math.signum(velocityX) != Math.signum(deltaX) && isFling) { 
100                 returnToOriginalPage = true; 
101             } 
102  
103             int finalPage; 
104             // We give flings precedence over large moves, which is why we short-circuit our 
105             // test for a large move if a fling has been registered. That is, a large 
106             // move to the left and fling to the right will register as a fling to the right. 
107             //朝右移动 
108             if (((isSignificantMove && deltaX > 0 && !isFling) || 
109                     (isFling && velocityX > 0)) && mCurrentPage > 0) { 
110                 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; 
111                 snapToPageWithVelocity(finalPage, velocityX); 
112             //朝左移动 
113             } else if (((isSignificantMove && deltaX < 0 && !isFling) || 
114                     (isFling && velocityX < 0)) && 
115                     mCurrentPage < getChildCount() - 1) { 
116                 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; 
117                 snapToPageWithVelocity(finalPage, velocityX); 
118             //寻找离屏幕中心最近的页面移动 
119             } else { 
120                 snapToDestination(); 
121             } 
122         } 
123          //直接移动到前一页 
124          else if (mTouchState == TOUCH_STATE_PREV_PAGE) { 
125             // at this point we have not moved beyond the touch slop 
126             // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 
127             // we can just page 
128             int nextPage = Math.max(0, mCurrentPage - 1); 
129             if (nextPage != mCurrentPage) { 
130                 snapToPage(nextPage); 
131             } else { 
132                 snapToDestination(); 
133             } 
134         } 
135          //直接移动到下一页 
136          else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { 
137             // at this point we have not moved beyond the touch slop 
138             // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 
139             // we can just page 
140             int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); 
141             if (nextPage != mCurrentPage) { 
142                 snapToPage(nextPage); 
143             } else { 
144                 snapToDestination(); 
145             } 
146         } else { 
147             onUnhandledTap(ev); 
148         } 
149         mTouchState = TOUCH_STATE_REST; 
150         mActivePointerId = INVALID_POINTER; 
151         releaseVelocityTracker(); 
152         break; 
153      //对事件不响应 
154     case MotionEvent.ACTION_CANCEL: 
155         if (mTouchState == TOUCH_STATE_SCROLLING) { 
156             snapToDestination(); 
157         } 
158         mTouchState = TOUCH_STATE_REST; 
159         mActivePointerId = INVALID_POINTER; 
160         releaseVelocityTracker(); 
161         break; 
162  
163     case MotionEvent.ACTION_POINTER_UP: 
164         onSecondaryPointerUp(ev); 
165         break; 
166     } 
167  
168     return true; 
169 }

scrollTo和scrollBy的区别

  我们查看View类的源代码如下所示,mScrollX记录的是当前View针对屏幕坐标在水平方向上的偏移量,而mScrollY则是记录的时当前View针对屏幕在竖值方向上的偏移 量。

  从以下代码我们可以得知:

  1. scrollTo就是把View移动到屏幕的X和Y位置,也就是绝对位置。
  2. 而scrollBy其实就是调用的scrollTo,但是参数是当前mScrollX和mScrollY加上X和Y的位置,所以ScrollBy调用的是相对于mScrollX和 mScrollY的位置。

  我们在上面的代码中可以看到当我们手指不放移动屏幕时,就会调用scrollBy来移动一段相对的距离。而当我们手指松开后,会调用 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); 来产生一段动画来移动到相应的页面,在这个过程中系统回不断调用computeScroll(),我们再使用scrollTo来把View移动到当前Scroller所在的绝对位置。

 1 /** 
 2    * Set the scrolled position of your view. This will cause a call to 
 3    * {@link #onScrollChanged(int, int, int, int)} and the view will be 
 4    * invalidated. 
 5    * @param x the x position to scroll to 
 6    * @param y the y position to scroll to 
 7    */ 
 8   public void scrollTo(int x, int y) { 
 9       if (mScrollX != x || mScrollY != y) { 
10           int oldX = mScrollX; 
11           int oldY = mScrollY; 
12           mScrollX = x; 
13           mScrollY = y; 
14           invalidateParentCaches(); 
15           onScrollChanged(mScrollX, mScrollY, oldX, oldY); 
16           if (!awakenScrollBars()) { 
17               invalidate(true); 
18           } 
19       } 
20   } 
21   /** 
22    * Move the scrolled position of your view. This will cause a call to 
23    * {@link #onScrollChanged(int, int, int, int)} and the view will be 
24    * invalidated. 
25    * @param x the amount of pixels to scroll by horizontally 
26    * @param y the amount of pixels to scroll by vertically 
27    */ 
28   public void scrollBy(int x, int y) { 
29       scrollTo(mScrollX + x, mScrollY + y); 
30   }

http://mobile.51cto.com/hot-316799.htm

Android4.0 Launcher 源码分析3——WorkSpace结构

标签:

原文地址:http://www.cnblogs.com/nathan909/p/5478247.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!