桌面的左右滑动功能主要是在 PagedView 类中实现的,而 WorkSpace 是 PagedView 类的子 类,所以会继承 PagedView 中的方法。当我们的手指点击 WorkSpace 时,首先就会触发 PageView 中的 onInterceptTouchEvent()方法,会根据相应的条件来判断是否对 Touch 事件进行拦截,
如果 onInterceptTouchEvent()方法返回为 true,则会对 Touch 事件进行拦截,PageView 类的 onTouch 方法会进行响应从而得到调用。如果返回 false,就分两钟情况:(1)我们是点击在它 的子控键上进行滑动时,比如我们是点击在桌面的图标上进行左右滑动的, workspace 则会把 Touch 事件分发给它的子控件。(2)而如果仅仅是点击到桌面的空白出 Touch 事件就不会发生响 应。
在我们手指第一次触摸到屏幕时,首先会对 onInterceptTouchEvent 中的事件进行判断,
如果是按下事件(MotionEvent.ACTION_DOWN), 则会记录按下时的 X 坐标、Y 坐标等等数据,同 时改变现在 Workspace 的状态为滚动状态(OUCH_STATE_SCROLLING),这时会返回 ture,把事件 交给 onTouchEvent 函数来处理,onTouchEvent 中同样会对事件类型进行判断,当事件方法为 (otionEvent.ACTION_DOWN)的时候,就可以开始显示滚动的指示条了(就是 Hotseat 上显示第几 屏的屏点)。当我们按着屏幕不放进行滑动的时候,又会在 onInterceptTouchEvent 进行事件拦 截,但是现在的事件类型变为了 MotionEvent.ACTION_MOVE,因为是移动的操作,所以会在拦 截的时候取消桌面长按的事件的响应,同时转到 onTouchEvent 中对 ACTION_MOVE 事件的响应中,
判断我们移动了多少距离,使用 scrollBy 方法来对桌面进行移动,并刷新屏幕。最后我们放开 手后会触发 onTouchEvent 中的 MotionEvent.ACTION_UP 事件,这时会根据滑动的情况来判断是 朝左滑动还是朝右滑动,如果手指只滑动了屏幕宽度的少一半距离,则会弹回原来的页面,滑 动多于屏幕宽度的一半则会进行翻页。同时要注意无论在什么情况下触发了 WorkSpace 滑动的 事件,则系统会不断调用 computeScroll()方法,我们重写这个方法同时在这个方法中调用刷 新界面等操作。
滑动过程中所要注意的主要方法如下,具体见代码注释。
1 //对 Touch 事件进行拦截 主要用于在拦截各种 Touch 事件时,设置 mTouchState 的各种状态 2 @Override
3 publicpublicpublicpublic booleanbooleanbooleanboolean 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,则 会调 用
14 // Skip touch handling if there are no pages to swipe 15 // 如果没有页面,则跳过操作。
16 ifififif (getChildCount() <= 0) returnreturnreturnreturn supersupersupersuper.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 finalfinalfinalfinal intintintint action = ev.getAction();
26 //如果是在 MOVE 的情况下,则进行 Touch 事件拦截 27 ifififif ((action == MotionEvent.ACTION_MOVE) &&
28 (mTouchState == TOUCH_STATE_SCROLLING)) { 29 returnreturnreturnreturn truetruetruetrue;
30 } 31
32 switchswitchswitchswitch (action & MotionEvent.ACTION_MASK) { 33 casecasecasecase 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 ifififif (mActivePointerId != INVALID_POINTER) {
40 //根据移动的距离判断是翻页还是移动一段位移,同时设置 lastMotionX 或者
mTouchState 这些值。同时取消桌面长按事件。
41 determineScrollingStart(ev);
42 breakbreakbreakbreak;
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 casecasecasecase MotionEvent.ACTION_DOWN: { 55 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX();
56 finalfinalfinalfinal floatfloatfloatfloat 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 = truetruetruetrue;
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 finalfinalfinalfinal intintintint xDist = Math.abs(mScroller.getFinalX() -mScroller.getCurrX());
80
81 finalfinalfinalfinal booleanbooleanbooleanbooleanfinishedScrolling = (mScroller.isFinished() || xDist
< mTouchSlop);
82 ifififif (finishedScrolling) {
83 //标记为 TOUCH_STATE_REST 状态 84 mTouchState = TOUCH_STATE_REST;
85 //取消滚动动画
86 mScroller.abortAnimation();
87 } elseelseelseelse {
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 ifififif (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState !=
TOUCH_STATE_NEXT_PAGE) {
96 ifififif (getChildCount() > 0) {
97 //根据触点的点位来判断是否点击到上一页,从而更新相应的状态
98 ifififif (hitsPreviousPage(x, y)) {
99 mTouchState = TOUCH_STATE_PREV_PAGE;
100 } elseelseelseelse ifififif (hitsNextPage(x, y)) {
101 mTouchState = TOUCH_STATE_NEXT_PAGE;
102 }
103 }
104 }
105 breakbreakbreakbreak;
106 }
107
108 casecasecasecase MotionEvent.ACTION_UP:
109 casecasecasecase MotionEvent.ACTION_CANCEL:
110 //触点不被相应时,所做的动作
111 mTouchState = TOUCH_STATE_REST;
112 mAllowLongPress = falsefalsefalsefalse;
113 mActivePointerId = INVALID_POINTER;
114 //释放速率跟踪
115 releaseVelocityTracker();
116 breakbreakbreakbreak;
117
118 casecasecasecase MotionEvent.ACTION_POINTER_UP:
119 onSecondaryPointerUp(ev);
120 releaseVelocityTracker();
121 breakbreakbreakbreak;
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 ifififif(DEBUG) Log.d(TAG, "onInterceptTouchEvent "+(mTouchState !=
TOUCH_STATE_REST));
130 //只要是 mTouchState 的状态不为 TOUCH_STATE_REST,那么就进行事件拦截 131 returnreturnreturnreturn mTouchState != TOUCH_STATE_REST;
132}
onTouchEvent 方法,详细见代码注释:
133@Override
134publicpublicpublicpublic booleanbooleanbooleanboolean onTouchEvent(MotionEvent ev) {
135 // Skip touch handling if there are no pages to swipe 136 // 如果没有子页面,就直接跳过
137 ifififif (getChildCount() <= 0) returnreturnreturnreturn supersupersupersuper.onTouchEvent(ev);
138
139 acquireVelocityTrackerAndAddMovement(ev);
140
141 finalfinalfinalfinal intintintint action = ev.getAction();
142
143 switchswitchswitchswitch (action & MotionEvent.ACTION_MASK) { 144 casecasecasecase MotionEvent.ACTION_DOWN:
145 /*
146 * If being flinged and user touches, stop the fling. isFinished 147 * will be false if being flinged.
148 * 如果在滑动的过程中下用户又点击桌面,则取消滑动,从而响应当前的点击。
149 * 在滑动的 isFinished 将返回 false.
150 */
151 ifififif (!mScroller.isFinished()) { 152 mScroller.abortAnimation();
153 }
154
155 // Remember where the motion event started 156 mDownMotionX = mLastMotionX = ev.getX();
157 mLastMotionXRemainder = 0;
158 mTotalMotionX = 0;
159 mActivePointerId = ev.getPointerId(0);
160 //主要用来显示滚动条,表明要开始滚动了,这里可以进行调整,滚动条时逐渐显示还是立
刻显示。
161 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 162 pageBeginMoving();
163 }
164 breakbreakbreakbreak;
165
166 casecasecasecase MotionEvent.ACTION_MOVE:
167 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 168 // Scroll to follow the motion event
169 finalfinalfinalfinal intintintint pointerIndex = ev.findPointerIndex(mActivePointerId);
170 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX(pointerIndex);
171 finalfinalfinalfinal floatfloatfloatfloat deltaX = mLastMotionX + mLastMotionXRemainder - x;
172 //总共移动的距离
173 mTotalMotionX += Math.abs(deltaX);
174
175 // Only scroll and update mLastMotionX if we have moved some discrete amount. We
176 // keep the remainder because we are actually testing if we've moved from the last
177 // scrolled position (which is discrete).
178 // 如 果 我 们 移 动 了 一 小 段 距 离 , 我 们 则 移 动 和 更 新 mLastMotionX 。 我 们 保 存 Remainder 变量是因为会检测我们
179
180 //是否是从最后的滚动点位移动的。
181 ifififif (Math.abs(deltaX) >= 1.0f) {
182 mTouchX += deltaX;
183 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
184 ifififif (!mDeferScrollUpdate) { 185 scrollBy((intintintint) deltaX, 0);
186 ifififif (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
187 } elseelseelseelse {
188 invalidate();
189 }
190 mLastMotionX = x;
191 mLastMotionXRemainder = deltaX - (intintintint) deltaX;
192 } elseelseelseelse {
193 //Trigger the scrollbars to draw. When invoked this method starts an animation to fade the
194 //scrollbars out after a default delay. If a subclass provides animated scrolling,
195 //the start delay should equal the duration of the scrolling animation.
196 //触发 scrollbar 进行绘制。 使用这个方法来启动一个动画来使 scrollbars 经过
203 breakbreakbreakbreak;
204
205 casecasecasecase MotionEvent.ACTION_UP:
206 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 207 finalfinalfinalfinal intintintint activePointerId = mActivePointerId;
208 finalfinalfinalfinal intintintint pointerIndex = ev.findPointerIndex(activePointerId);
209 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX(pointerIndex);
210 finalfinalfinalfinal VelocityTracker velocityTracker = mVelocityTracker;
211 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
212 intintintint velocityX = (intintintint)
velocityTracker.getXVelocity(activePointerId);
213 finalfinalfinalfinal intintintint deltaX = (intintintint) (x - mDownMotionX);
214 finalfinalfinalfinal intintintint pageWidth =
getScaledMeasuredWidth(getPageAt(mCurrentPage));
215 // 屏幕的宽度*0.4f
216 booleanbooleanbooleanboolean isSignificantMove = Math.abs(deltaX) > pageWidth *
217 SIGNIFICANT_MOVE_THRESHOLD;
218 finalfinalfinalfinal intintintint snapVelocity = mSnapVelocity;
219
220 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder -x);
221
222 booleanbooleanbooleanboolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
223 Math.abs(velocityX) > snapVelocity;
224
225 // In the case that the page is moved far to one direction and then is flung
226 // in the opposite direction, we use a threshold to determine whether we should
227 // just return to the starting page, or if we should skip one further.
228 // 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。我们使用一个阀值
来判断是进行翻页还是返回到初始页面
229 booleanbooleanbooleanboolean returnToOriginalPage = falsefalsefalsefalse;
230 ifififif (Math.abs(deltaX) > pageWidth *
RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
231 Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
232 returnToOriginalPage = truetruetruetrue;
233 }
234
235 intintintint finalPage;
236 // We give flings precedence over large moves, which is why we short-circuit our
237 // test for a large move if a fling has been registered. That is, a large
238 // move to the left and fling to the right will register as a fling to the right.
239 //朝右移动
240 ifififif (((isSignificantMove && deltaX > 0 && !isFling) ||
241 (isFling && velocityX > 0)) && mCurrentPage > 0) {
242 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
243 snapToPageWithVelocity(finalPage, velocityX);
244 //朝左移动
245 } elseelseelseelse ifififif (((isSignificantMove && deltaX < 0 && !isFling) ||
246 (isFling && velocityX < 0)) &&
247 mCurrentPage < getChildCount() - 1) {
248 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
249 snapToPageWithVelocity(finalPage, velocityX);
250 //寻找离屏幕中心最近的页面移动
256 elseelseelseelse ifififif (mTouchState == TOUCH_STATE_PREV_PAGE) {
257 // at this point we have not moved beyond the touch slop 258 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 259 // we can just page
260 intintintint nextPage = Math.max(0, mCurrentPage - 1);
261 ifififif (nextPage != mCurrentPage) { 262 snapToPage(nextPage);
268 elseelseelseelse ifififif (mTouchState == TOUCH_STATE_NEXT_PAGE) {
269 // at this point we have not moved beyond the touch slop 270 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 271 // we can just page
272 intintintint nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
273 ifififif (nextPage != mCurrentPage) { 274 snapToPage(nextPage);
275 } elseelseelseelse {
276 snapToDestination();
277 }
278 } elseelseelseelse {
279 onUnhandledTap(ev);
280 }
281 mTouchState = TOUCH_STATE_REST;
282 mActivePointerId = INVALID_POINTER;
283 releaseVelocityTracker();
284 breakbreakbreakbreak;
285 //对事件不响应
286 casecasecasecase MotionEvent.ACTION_CANCEL:
287 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 288 snapToDestination();
289 }
290 mTouchState = TOUCH_STATE_REST;
291 mActivePointerId = INVALID_POINTER;
292 releaseVelocityTracker();
293 breakbreakbreakbreak;
294
295 casecasecasecase MotionEvent.ACTION_POINTER_UP:
296 onSecondaryPointerUp(ev);
297 breakbreakbreakbreak;
298 } 299
300 returnreturnreturnreturn truetruetruetrue;
301}
最后有个小知识点要搞清楚,不少网友都问到过我。就是 scrollTo 和 scrollBy 的区别。
我们查看 View 类的源代码如下所示,mScrollX 记录的是当前 View 针对屏幕坐标在水平方向上 的偏移量,而 mScrollY 则是记录的时当前 View 针对屏幕在竖值方向上的偏移量。
从以下代码我们可以得知,scrollTo 就是把 View 移动到屏幕的 X 和 Y 位置,也就是绝对 位置。而 scrollBy 其实就是调用的 scrollTo,但是参数是当前 mScrollX 和 mScrollY 加上 X 和 Y 的位置,所以 ScrollBy 调用的是相对于 mScrollX 和 mScrollY 的位置。我们在上面的代码 中可以看到当我们手指不放移动屏幕时,就会调用 scrollBy 来移动一段相对的距离。而当我们 手指松开后,会调用 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
来产生一段动画来移动到相应的页面,在这个过程中系统回不断调用 computeScroll(),我们 再使用 scrollTo 来把 View 移动到当前 Scroller 所在的绝对位置。
302/**
303 * Set the scrolled position of your view. This will cause a call to 304 * {@link #onScrollChanged(int, int, int, int)} and the view will be 305 * invalidated.
306 * @param x the x position to scroll to 307 * @param y the y position to scroll to
308 */
309 publicpublicpublicpublic voidvoidvoidvoid scrollTo(intintintint x, intintintint y) { 310 ifififif (mScrollX != x || mScrollY != y) { 311 intintintint oldX = mScrollX;
312 intintintint oldY = mScrollY;
313 mScrollX = x;
314 mScrollY = y;
315 invalidateParentCaches();
316 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
317 ifififif (!awakenScrollBars()) { 318 invalidate(truetruetruetrue);
319 }
320 } 321 } 322 /**
323 * Move the scrolled position of your view. This will cause a call to 324 * {@link #onScrollChanged(int, int, int, int)} and the view will be 325 * invalidated.
326 * @param x the amount of pixels to scroll by horizontally 327 * @param y the amount of pixels to scroll by vertically 328 */
329 publicpublicpublicpublic voidvoidvoidvoid scrollBy(intintintint x, intintintint y) { 330 scrollTo(mScrollX + x, mScrollY + y);
331 }
【编辑推荐】