1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher2; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Point; 22 import android.graphics.Rect; 23 import android.graphics.RectF; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Vibrator; 27 import android.util.Log; 28 import android.view.KeyEvent; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewConfiguration; 32 import android.view.inputmethod.InputMethodManager; 33 34 import com.android.launcher.R; 35 36 import java.util.ArrayList; 37 38 /** 39 * Class for initiating a drag within a view or across multiple views. 40 */ 41 public class DragController { 42 @SuppressWarnings({"UnusedDeclaration"}) 43 private static final String TAG = "Launcher.DragController"; 44 45 /** Indicates the drag is a move. */ 46 public static int DRAG_ACTION_MOVE = 0; 47 48 /** Indicates the drag is a copy. */ 49 public static int DRAG_ACTION_COPY = 1; 50 51 private static final int SCROLL_DELAY = 600; 52 private static final int VIBRATE_DURATION = 35; 53 54 private static final boolean PROFILE_DRAWING_DURING_DRAG = false; 55 56 private static final int SCROLL_OUTSIDE_ZONE = 0; 57 private static final int SCROLL_WAITING_IN_ZONE = 1; 58 59 static final int SCROLL_NONE = -1; 60 static final int SCROLL_LEFT = 0; 61 static final int SCROLL_RIGHT = 1; 62 63 private Launcher mLauncher; 64 private Handler mHandler; 65 private final Vibrator mVibrator = new Vibrator(); 66 67 // temporaries to avoid gc thrash 68 private Rect mRectTemp = new Rect(); 69 private final int[] mCoordinatesTemp = new int[2]; 70 71 /** Whether or not we're dragging. */ 72 private boolean mDragging; 73 74 /** X coordinate of the down event. */ 75 private int mMotionDownX; 76 77 /** Y coordinate of the down event. */ 78 private int mMotionDownY; 79 80 /** the area at the edge of the screen that makes the workspace go left 81 * or right while you're dragging. 82 */ 83 private int mScrollZone; 84 85 private DropTarget.DragObject mDragObject; 86 87 /** Who can receive drop events */ 88 private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>(); 89 90 private ArrayList<DragListener> mListeners = new ArrayList<DragListener>(); 91 92 /** The window token used as the parent for the DragView. */ 93 private IBinder mWindowToken; 94 95 /** The view that will be scrolled when dragging to the left and right edges of the screen. */ 96 private View mScrollView; 97 98 private View mMoveTarget; 99 100 private DragScroller mDragScroller; 101 private int mScrollState = SCROLL_OUTSIDE_ZONE; 102 private ScrollRunnable mScrollRunnable = new ScrollRunnable(); 103 104 private RectF mDeleteRegion; 105 private DropTarget mLastDropTarget; 106 107 private InputMethodManager mInputMethodManager; 108 109 private int mLastTouch[] = new int[2]; 110 private int mDistanceSinceScroll = 0; 111 112 private int mTmpPoint[] = new int[2]; 113 private Rect mDragLayerRect = new Rect(); 114 115 /** 116 * Interface to receive notifications when a drag starts or stops 117 */ 118 interface DragListener { 119 120 /** 121 * A drag has begun 122 * 123 * @param source An object representing where the drag originated 124 * @param info The data associated with the object that is being dragged 125 * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} 126 * or {@link DragController#DRAG_ACTION_COPY} 127 */ 128 void onDragStart(DragSource source, Object info, int dragAction); 129 130 /** 131 * The drag has ended 132 */ 133 void onDragEnd(); 134 } 135 136 /** 137 * Used to create a new DragLayer from XML. 138 * 139 * @param context The application's context. 140 */ 141 public DragController(Launcher launcher) { 142 mLauncher = launcher; 143 mHandler = new Handler(); 144 mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone); 145 } 146 147 public boolean dragging() { 148 return mDragging; 149 } 150 151 /** 152 * Starts a drag. 153 * 154 * @param v The view that is being dragged 155 * @param source An object representing where the drag originated 156 * @param dragInfo The data associated with the object that is being dragged 157 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 158 * {@link #DRAG_ACTION_COPY} 159 */ 160 public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) { 161 startDrag(v, source, dragInfo, dragAction, null); 162 } 163 164 /** 165 * Starts a drag. 166 * 167 * @param v The view that is being dragged 168 * @param source An object representing where the drag originated 169 * @param dragInfo The data associated with the object that is being dragged 170 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 171 * {@link #DRAG_ACTION_COPY} 172 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 173 * Makes dragging feel more precise, e.g. you can clip out a transparent border 174 */ 175 public void startDrag(View v, DragSource source, Object dragInfo, int dragAction, 176 Rect dragRegion) { 177 Bitmap b = getViewBitmap(v); 178 179 if (b == null) { 180 // out of memory? 181 return; 182 } 183 184 int[] loc = mCoordinatesTemp; 185 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 186 int dragLayerX = loc[0]; 187 int dragLayerY = loc[1]; 188 189 startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion); 190 b.recycle(); 191 192 if (dragAction == DRAG_ACTION_MOVE) { 193 v.setVisibility(View.GONE); 194 } 195 } 196 197 /** 198 * Starts a drag. 199 * 200 * @param v The view that is being dragged 201 * @param bmp The bitmap that represents the view being dragged 202 * @param source An object representing where the drag originated 203 * @param dragInfo The data associated with the object that is being dragged 204 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 205 * {@link #DRAG_ACTION_COPY} 206 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 207 * Makes dragging feel more precise, e.g. you can clip out a transparent border 208 */ 209 public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, 210 Rect dragRegion) { 211 int[] loc = mCoordinatesTemp; 212 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 213 int dragLayerX = loc[0]; 214 int dragLayerY = loc[1]; 215 216 startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion); 217 218 if (dragAction == DRAG_ACTION_MOVE) { 219 v.setVisibility(View.GONE); 220 } 221 } 222 223 /** 224 * Starts a drag. 225 * 226 * @param b The bitmap to display as the drag image. It will be re-scaled to the 227 * enlarged size. 228 * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. 229 * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. 230 * @param source An object representing where the drag originated 231 * @param dragInfo The data associated with the object that is being dragged 232 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 233 * {@link #DRAG_ACTION_COPY} 234 */ 235 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY, 236 DragSource source, Object dragInfo, int dragAction) { 237 startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, null); 238 } 239 240 /** 241 * Starts a drag. 242 * 243 * @param b The bitmap to display as the drag image. It will be re-scaled to the 244 * enlarged size. 245 * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. 246 * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. 247 * @param source An object representing where the drag originated 248 * @param dragInfo The data associated with the object that is being dragged 249 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 250 * {@link #DRAG_ACTION_COPY} 251 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 252 * Makes dragging feel more precise, e.g. you can clip out a transparent border 253 */ 254 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY, 255 DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) { 256 if (PROFILE_DRAWING_DURING_DRAG) { 257 android.os.Debug.startMethodTracing("Launcher"); 258 } 259 260 // Hide soft keyboard, if visible 261 if (mInputMethodManager == null) { 262 mInputMethodManager = (InputMethodManager) 263 mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); 264 } 265 mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); 266 267 for (DragListener listener : mListeners) { 268 listener.onDragStart(source, dragInfo, dragAction); 269 } 270 271 final int registrationX = mMotionDownX - dragLayerX; 272 final int registrationY = mMotionDownY - dragLayerY; 273 274 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 275 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 276 277 mDragging = true; 278 279 mDragObject = new DropTarget.DragObject(); 280 281 mDragObject.dragComplete = false; 282 mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); 283 mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); 284 mDragObject.dragSource = source; 285 mDragObject.dragInfo = dragInfo; 286 287 mVibrator.vibrate(VIBRATE_DURATION); 288 289 final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, 290 registrationY, 0, 0, b.getWidth(), b.getHeight()); 291 292 if (dragOffset != null) { 293 dragView.setDragVisualizeOffset(new Point(dragOffset)); 294 } 295 if (dragRegion != null) { 296 dragView.setDragRegion(new Rect(dragRegion)); 297 } 298 299 dragView.show(mMotionDownX, mMotionDownY); 300 handleMoveEvent(mMotionDownX, mMotionDownY); 301 } 302 303 /** 304 * Draw the view into a bitmap. 305 */ 306 Bitmap getViewBitmap(View v) { 307 v.clearFocus(); 308 v.setPressed(false); 309 310 boolean willNotCache = v.willNotCacheDrawing(); 311 v.setWillNotCacheDrawing(false); 312 313 // Reset the drawing cache background color to fully transparent 314 // for the duration of this operation 315 int color = v.getDrawingCacheBackgroundColor(); 316 v.setDrawingCacheBackgroundColor(0); 317 float alpha = v.getAlpha(); 318 v.setAlpha(1.0f); 319 320 if (color != 0) { 321 v.destroyDrawingCache(); 322 } 323 v.buildDrawingCache(); 324 Bitmap cacheBitmap = v.getDrawingCache(); 325 if (cacheBitmap == null) { 326 Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); 327 return null; 328 } 329 330 Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); 331 332 // Restore the view 333 v.destroyDrawingCache(); 334 v.setAlpha(alpha); 335 v.setWillNotCacheDrawing(willNotCache); 336 v.setDrawingCacheBackgroundColor(color); 337 338 return bitmap; 339 } 340 341 /** 342 * Call this from a drag source view like this: 343 * 344 * <pre> 345 * @Override 346 * public boolean dispatchKeyEvent(KeyEvent event) { 347 * return mDragController.dispatchKeyEvent(this, event) 348 * || super.dispatchKeyEvent(event); 349 * </pre> 350 */ 351 @SuppressWarnings({"UnusedDeclaration"}) 352 public boolean dispatchKeyEvent(KeyEvent event) { 353 return mDragging; 354 } 355 356 public boolean isDragging() { 357 return mDragging; 358 } 359 360 /** 361 * Stop dragging without dropping. 362 */ 363 public void cancelDrag() { 364 if (mDragging) { 365 if (mLastDropTarget != null) { 366 mLastDropTarget.onDragExit(mDragObject); 367 } 368 mDragObject.cancelled = true; 369 mDragObject.dragComplete = true; 370 mDragObject.dragSource.onDropCompleted(null, mDragObject, false); 371 } 372 endDrag(); 373 } 374 public void onAppsRemoved(ArrayList<ApplicationInfo> apps, Context context) { 375 // Cancel the current drag if we are removing an app that we are dragging 376 if (mDragObject != null) { 377 Object rawDragInfo = mDragObject.dragInfo; 378 if (rawDragInfo instanceof ShortcutInfo) { 379 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; 380 for (ApplicationInfo info : apps) { 381 if (dragInfo.intent.getComponent().equals(info.intent.getComponent())) { 382 cancelDrag(); 383 return; 384 } 385 } 386 } 387 } 388 } 389 390 private void endDrag() { 391 if (mDragging) { 392 mDragging = false; 393 for (DragListener listener : mListeners) { 394 listener.onDragEnd(); 395 } 396 if (mDragObject.dragView != null) { 397 mDragObject.dragView.remove(); 398 mDragObject.dragView = null; 399 } 400 } 401 } 402 403 /** 404 * Clamps the position to the drag layer bounds. 405 */ 406 private int[] getClampedDragLayerPos(float x, float y) { 407 mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); 408 mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); 409 mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); 410 return mTmpPoint; 411 } 412 413 /** 414 * Call this from a drag source view. 415 */ 416 public boolean onInterceptTouchEvent(MotionEvent ev) { 417 if (false) { 418 Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" 419 + mDragging); 420 } 421 final int action = ev.getAction(); 422 423 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 424 final int dragLayerX = dragLayerPos[0]; 425 final int dragLayerY = dragLayerPos[1]; 426 427 switch (action) { 428 case MotionEvent.ACTION_MOVE: 429 break; 430 case MotionEvent.ACTION_DOWN: 431 // Remember location of down touch 432 mMotionDownX = dragLayerX; 433 mMotionDownY = dragLayerY; 434 mLastDropTarget = null; 435 break; 436 case MotionEvent.ACTION_UP: 437 if (mDragging) { 438 drop(dragLayerX, dragLayerY); 439 } 440 endDrag(); 441 break; 442 case MotionEvent.ACTION_CANCEL: 443 cancelDrag(); 444 break; 445 } 446 447 return mDragging; 448 } 449 450 /** 451 * Sets the view that should handle move events. 452 */ 453 void setMoveTarget(View view) { 454 mMoveTarget = view; 455 } 456 457 public boolean dispatchUnhandledMove(View focused, int direction) { 458 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 459 } 460 461 private void handleMoveEvent(int x, int y) { 462 mDragObject.dragView.move(x, y); 463 464 // Drop on someone? 465 final int[] coordinates = mCoordinatesTemp; 466 DropTarget dropTarget = findDropTarget(x, y, coordinates); 467 mDragObject.x = coordinates[0]; 468 mDragObject.y = coordinates[1]; 469 if (dropTarget != null) { 470 DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); 471 if (delegate != null) { 472 dropTarget = delegate; 473 } 474 475 if (mLastDropTarget != dropTarget) { 476 if (mLastDropTarget != null) { 477 mLastDropTarget.onDragExit(mDragObject); 478 } 479 dropTarget.onDragEnter(mDragObject); 480 } 481 dropTarget.onDragOver(mDragObject); 482 } else { 483 if (mLastDropTarget != null) { 484 mLastDropTarget.onDragExit(mDragObject); 485 } 486 } 487 mLastDropTarget = dropTarget; 488 489 // Scroll, maybe, but not if we're in the delete region. 490 boolean inDeleteRegion = false; 491 if (mDeleteRegion != null) { 492 inDeleteRegion = mDeleteRegion.contains(x, y); 493 } 494 495 // After a scroll, the touch point will still be in the scroll region. 496 // Rather than scrolling immediately, require a bit of twiddling to scroll again 497 final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); 498 mDistanceSinceScroll += 499 Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); 500 mLastTouch[0] = x; 501 mLastTouch[1] = y; 502 503 if (!inDeleteRegion && x < mScrollZone) { 504 if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) { 505 mScrollState = SCROLL_WAITING_IN_ZONE; 506 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { 507 mScrollRunnable.setDirection(SCROLL_LEFT); 508 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 509 } 510 } 511 } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) { 512 if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) { 513 mScrollState = SCROLL_WAITING_IN_ZONE; 514 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) { 515 mScrollRunnable.setDirection(SCROLL_RIGHT); 516 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 517 } 518 } 519 } else { 520 if (mScrollState == SCROLL_WAITING_IN_ZONE) { 521 mScrollState = SCROLL_OUTSIDE_ZONE; 522 mScrollRunnable.setDirection(SCROLL_RIGHT); 523 mHandler.removeCallbacks(mScrollRunnable); 524 mDragScroller.onExitScrollArea(); 525 } 526 } 527 } 528 529 /** 530 * Call this from a drag source view. 531 */ 532 public boolean onTouchEvent(MotionEvent ev) { 533 if (!mDragging) { 534 return false; 535 } 536 537 final int action = ev.getAction(); 538 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 539 final int dragLayerX = dragLayerPos[0]; 540 final int dragLayerY = dragLayerPos[1]; 541 542 switch (action) { 543 case MotionEvent.ACTION_DOWN: 544 // Remember where the motion event started 545 mMotionDownX = dragLayerX; 546 mMotionDownY = dragLayerY; 547 548 if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { 549 mScrollState = SCROLL_WAITING_IN_ZONE; 550 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 551 } else { 552 mScrollState = SCROLL_OUTSIDE_ZONE; 553 } 554 break; 555 case MotionEvent.ACTION_MOVE: 556 handleMoveEvent(dragLayerX, dragLayerY); 557 break; 558 case MotionEvent.ACTION_UP: 559 // Ensure that we've processed a move event at the current pointer location. 560 handleMoveEvent(dragLayerX, dragLayerY); 561 562 mHandler.removeCallbacks(mScrollRunnable); 563 if (mDragging) { 564 drop(dragLayerX, dragLayerY); 565 } 566 endDrag(); 567 break; 568 case MotionEvent.ACTION_CANCEL: 569 cancelDrag(); 570 break; 571 } 572 573 return true; 574 } 575 576 private void drop(float x, float y) { 577 final int[] coordinates = mCoordinatesTemp; 578 final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); 579 580 mDragObject.x = coordinates[0]; 581 mDragObject.y = coordinates[1]; 582 boolean accepted = false; 583 if (dropTarget != null) { 584 mDragObject.dragComplete = true; 585 dropTarget.onDragExit(mDragObject); 586 if (dropTarget.acceptDrop(mDragObject)) { 587 dropTarget.onDrop(mDragObject); 588 accepted = true; 589 } 590 } 591 mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted); 592 } 593 594 private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { 595 final Rect r = mRectTemp; 596 597 final ArrayList<DropTarget> dropTargets = mDropTargets; 598 final int count = dropTargets.size(); 599 for (int i=count-1; i>=0; i--) { 600 DropTarget target = dropTargets.get(i); 601 if (!target.isDropEnabled()) 602 continue; 603 604 target.getHitRect(r); 605 606 // Convert the hit rect to DragLayer coordinates 607 target.getLocationInDragLayer(dropCoordinates); 608 r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); 609 610 mDragObject.x = x; 611 mDragObject.y = y; 612 if (r.contains(x, y)) { 613 DropTarget delegate = target.getDropTargetDelegate(mDragObject); 614 if (delegate != null) { 615 target = delegate; 616 target.getLocationInDragLayer(dropCoordinates); 617 } 618 619 // Make dropCoordinates relative to the DropTarget 620 dropCoordinates[0] = x - dropCoordinates[0]; 621 dropCoordinates[1] = y - dropCoordinates[1]; 622 623 return target; 624 } 625 } 626 return null; 627 } 628 629 public void setDragScoller(DragScroller scroller) { 630 mDragScroller = scroller; 631 } 632 633 public void setWindowToken(IBinder token) { 634 mWindowToken = token; 635 } 636 637 /** 638 * Sets the drag listner which will be notified when a drag starts or ends. 639 */ 640 public void addDragListener(DragListener l) { 641 mListeners.add(l); 642 } 643 644 /** 645 * Remove a previously installed drag listener. 646 */ 647 public void removeDragListener(DragListener l) { 648 mListeners.remove(l); 649 } 650 651 /** 652 * Add a DropTarget to the list of potential places to receive drop events. 653 */ 654 public void addDropTarget(DropTarget target) { 655 mDropTargets.add(target); 656 } 657 658 /** 659 * Don't send drop events to <em>target</em> any more. 660 */ 661 public void removeDropTarget(DropTarget target) { 662 mDropTargets.remove(target); 663 } 664 665 /** 666 * Set which view scrolls for touch events near the edge of the screen. 667 */ 668 public void setScrollView(View v) { 669 mScrollView = v; 670 } 671 672 /** 673 * Specifies the delete region. We won't scroll on touch events over the delete region. 674 * 675 * @param region The rectangle in DragLayer coordinates of the delete region. 676 */ 677 void setDeleteRegion(RectF region) { 678 mDeleteRegion = region; 679 } 680 681 DragView getDragView() { 682 return mDragObject.dragView; 683 } 684 685 private class ScrollRunnable implements Runnable { 686 private int mDirection; 687 688 ScrollRunnable() { 689 } 690 691 public void run() { 692 if (mDragScroller != null) { 693 if (mDirection == SCROLL_LEFT) { 694 mDragScroller.scrollLeft(); 695 } else { 696 mDragScroller.scrollRight(); 697 } 698 mScrollState = SCROLL_OUTSIDE_ZONE; 699 mDistanceSinceScroll = 0; 700 mDragScroller.onExitScrollArea(); 701 } 702 } 703 704 void setDirection(int direction) { 705 mDirection = direction; 706 } 707 } 708 } 709