DragController.java revision 9658b1e65932f0149a596f451e0bd966e7ee4e74
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 17package com.android.launcher2; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Point; 22import android.graphics.PointF; 23import android.graphics.Rect; 24import android.os.Handler; 25import android.os.IBinder; 26import android.os.Vibrator; 27import android.util.Log; 28import android.view.KeyEvent; 29import android.view.MotionEvent; 30import android.view.VelocityTracker; 31import android.view.View; 32import android.view.ViewConfiguration; 33import android.view.inputmethod.InputMethodManager; 34 35import com.android.launcher.R; 36 37import java.util.ArrayList; 38 39/** 40 * Class for initiating a drag within a view or across multiple views. 41 */ 42public class DragController { 43 @SuppressWarnings({"UnusedDeclaration"}) 44 private static final String TAG = "Launcher.DragController"; 45 46 /** Indicates the drag is a move. */ 47 public static int DRAG_ACTION_MOVE = 0; 48 49 /** Indicates the drag is a copy. */ 50 public static int DRAG_ACTION_COPY = 1; 51 52 private static final int SCROLL_DELAY = 500; 53 private static final int RESCROLL_DELAY = 750; 54 private static final int VIBRATE_DURATION = 15; 55 56 private static final boolean PROFILE_DRAWING_DURING_DRAG = false; 57 58 private static final int SCROLL_OUTSIDE_ZONE = 0; 59 private static final int SCROLL_WAITING_IN_ZONE = 1; 60 61 static final int SCROLL_NONE = -1; 62 static final int SCROLL_LEFT = 0; 63 static final int SCROLL_RIGHT = 1; 64 65 private static final float MAX_FLING_DEGREES = 35f; 66 private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1500; 67 68 private Launcher mLauncher; 69 private Handler mHandler; 70 private final Vibrator mVibrator = new Vibrator(); 71 72 // temporaries to avoid gc thrash 73 private Rect mRectTemp = new Rect(); 74 private final int[] mCoordinatesTemp = new int[2]; 75 76 /** Whether or not we're dragging. */ 77 private boolean mDragging; 78 79 /** X coordinate of the down event. */ 80 private int mMotionDownX; 81 82 /** Y coordinate of the down event. */ 83 private int mMotionDownY; 84 85 /** the area at the edge of the screen that makes the workspace go left 86 * or right while you're dragging. 87 */ 88 private int mScrollZone; 89 90 private DropTarget.DragObject mDragObject; 91 92 /** Who can receive drop events */ 93 private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>(); 94 private ArrayList<DragListener> mListeners = new ArrayList<DragListener>(); 95 private DropTarget mFlingToDeleteDropTarget; 96 97 /** The window token used as the parent for the DragView. */ 98 private IBinder mWindowToken; 99 100 /** The view that will be scrolled when dragging to the left and right edges of the screen. */ 101 private View mScrollView; 102 103 private View mMoveTarget; 104 105 private DragScroller mDragScroller; 106 private int mScrollState = SCROLL_OUTSIDE_ZONE; 107 private ScrollRunnable mScrollRunnable = new ScrollRunnable(); 108 109 private DropTarget mLastDropTarget; 110 111 private InputMethodManager mInputMethodManager; 112 113 private int mLastTouch[] = new int[2]; 114 private int mDistanceSinceScroll = 0; 115 116 private int mTmpPoint[] = new int[2]; 117 private Rect mDragLayerRect = new Rect(); 118 119 protected int mFlingToDeleteThresholdVelocity; 120 private VelocityTracker mVelocityTracker; 121 122 /** 123 * Interface to receive notifications when a drag starts or stops 124 */ 125 interface DragListener { 126 127 /** 128 * A drag has begun 129 * 130 * @param source An object representing where the drag originated 131 * @param info The data associated with the object that is being dragged 132 * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} 133 * or {@link DragController#DRAG_ACTION_COPY} 134 */ 135 void onDragStart(DragSource source, Object info, int dragAction); 136 137 /** 138 * The drag has ended 139 */ 140 void onDragEnd(); 141 } 142 143 /** 144 * Used to create a new DragLayer from XML. 145 * 146 * @param context The application's context. 147 */ 148 public DragController(Launcher launcher) { 149 mLauncher = launcher; 150 mHandler = new Handler(); 151 mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone); 152 mVelocityTracker = VelocityTracker.obtain(); 153 154 float density = launcher.getResources().getDisplayMetrics().density; 155 mFlingToDeleteThresholdVelocity = (int) (FLING_TO_DELETE_THRESHOLD_Y_VELOCITY * density); 156 } 157 158 public boolean dragging() { 159 return mDragging; 160 } 161 162 /** 163 * Starts a drag. 164 * 165 * @param v The view that is being dragged 166 * @param source An object representing where the drag originated 167 * @param dragInfo The data associated with the object that is being dragged 168 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 169 * {@link #DRAG_ACTION_COPY} 170 */ 171 public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) { 172 startDrag(v, source, dragInfo, dragAction, null); 173 } 174 175 /** 176 * Starts a drag. 177 * 178 * @param v The view that is being dragged 179 * @param source An object representing where the drag originated 180 * @param dragInfo The data associated with the object that is being dragged 181 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 182 * {@link #DRAG_ACTION_COPY} 183 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 184 * Makes dragging feel more precise, e.g. you can clip out a transparent border 185 */ 186 public void startDrag(View v, DragSource source, Object dragInfo, int dragAction, 187 Rect dragRegion) { 188 Bitmap b = getViewBitmap(v); 189 190 if (b == null) { 191 // out of memory? 192 return; 193 } 194 195 int[] loc = mCoordinatesTemp; 196 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 197 int dragLayerX = loc[0]; 198 int dragLayerY = loc[1]; 199 200 startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, 1f); 201 b.recycle(); 202 203 if (dragAction == DRAG_ACTION_MOVE) { 204 v.setVisibility(View.GONE); 205 } 206 } 207 208 /** 209 * Starts a drag. 210 * 211 * @param v The view that is being dragged 212 * @param bmp The bitmap that represents the view being dragged 213 * @param source An object representing where the drag originated 214 * @param dragInfo The data associated with the object that is being dragged 215 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 216 * {@link #DRAG_ACTION_COPY} 217 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 218 * Makes dragging feel more precise, e.g. you can clip out a transparent border 219 */ 220 public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, 221 Rect dragRegion, float initialDragViewScale) { 222 int[] loc = mCoordinatesTemp; 223 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 224 int dragLayerX = loc[0] + v.getPaddingLeft() + 225 (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); 226 int dragLayerY = loc[1] + v.getPaddingTop() + 227 (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); 228 229 startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, 230 initialDragViewScale); 231 232 if (dragAction == DRAG_ACTION_MOVE) { 233 v.setVisibility(View.GONE); 234 } 235 } 236 237 /** 238 * Starts a drag. 239 * 240 * @param b The bitmap to display as the drag image. It will be re-scaled to the 241 * enlarged size. 242 * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. 243 * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. 244 * @param source An object representing where the drag originated 245 * @param dragInfo The data associated with the object that is being dragged 246 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 247 * {@link #DRAG_ACTION_COPY} 248 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 249 * Makes dragging feel more precise, e.g. you can clip out a transparent border 250 */ 251 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY, 252 DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, 253 float initialDragViewScale) { 254 if (PROFILE_DRAWING_DURING_DRAG) { 255 android.os.Debug.startMethodTracing("Launcher"); 256 } 257 258 // Hide soft keyboard, if visible 259 if (mInputMethodManager == null) { 260 mInputMethodManager = (InputMethodManager) 261 mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); 262 } 263 mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); 264 265 for (DragListener listener : mListeners) { 266 listener.onDragStart(source, dragInfo, dragAction); 267 } 268 269 final int registrationX = mMotionDownX - dragLayerX; 270 final int registrationY = mMotionDownY - dragLayerY; 271 272 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 273 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 274 275 mDragging = true; 276 277 mDragObject = new DropTarget.DragObject(); 278 279 mDragObject.dragComplete = false; 280 mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); 281 mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); 282 mDragObject.dragSource = source; 283 mDragObject.dragInfo = dragInfo; 284 285 mVibrator.vibrate(VIBRATE_DURATION); 286 287 final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, 288 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); 289 290 if (dragOffset != null) { 291 dragView.setDragVisualizeOffset(new Point(dragOffset)); 292 } 293 if (dragRegion != null) { 294 dragView.setDragRegion(new Rect(dragRegion)); 295 } 296 297 dragView.show(mMotionDownX, mMotionDownY); 298 handleMoveEvent(mMotionDownX, mMotionDownY); 299 } 300 301 /** 302 * Draw the view into a bitmap. 303 */ 304 Bitmap getViewBitmap(View v) { 305 v.clearFocus(); 306 v.setPressed(false); 307 308 boolean willNotCache = v.willNotCacheDrawing(); 309 v.setWillNotCacheDrawing(false); 310 311 // Reset the drawing cache background color to fully transparent 312 // for the duration of this operation 313 int color = v.getDrawingCacheBackgroundColor(); 314 v.setDrawingCacheBackgroundColor(0); 315 float alpha = v.getAlpha(); 316 v.setAlpha(1.0f); 317 318 if (color != 0) { 319 v.destroyDrawingCache(); 320 } 321 v.buildDrawingCache(); 322 Bitmap cacheBitmap = v.getDrawingCache(); 323 if (cacheBitmap == null) { 324 Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); 325 return null; 326 } 327 328 Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); 329 330 // Restore the view 331 v.destroyDrawingCache(); 332 v.setAlpha(alpha); 333 v.setWillNotCacheDrawing(willNotCache); 334 v.setDrawingCacheBackgroundColor(color); 335 336 return bitmap; 337 } 338 339 /** 340 * Call this from a drag source view like this: 341 * 342 * <pre> 343 * @Override 344 * public boolean dispatchKeyEvent(KeyEvent event) { 345 * return mDragController.dispatchKeyEvent(this, event) 346 * || super.dispatchKeyEvent(event); 347 * </pre> 348 */ 349 @SuppressWarnings({"UnusedDeclaration"}) 350 public boolean dispatchKeyEvent(KeyEvent event) { 351 return mDragging; 352 } 353 354 public boolean isDragging() { 355 return mDragging; 356 } 357 358 /** 359 * Stop dragging without dropping. 360 */ 361 public void cancelDrag() { 362 if (mDragging) { 363 if (mLastDropTarget != null) { 364 mLastDropTarget.onDragExit(mDragObject); 365 } 366 mDragObject.deferDragViewCleanupPostAnimation = false; 367 mDragObject.cancelled = true; 368 mDragObject.dragComplete = true; 369 mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); 370 } 371 endDrag(); 372 } 373 public void onAppsRemoved(ArrayList<ApplicationInfo> apps, Context context) { 374 // Cancel the current drag if we are removing an app that we are dragging 375 if (mDragObject != null) { 376 Object rawDragInfo = mDragObject.dragInfo; 377 if (rawDragInfo instanceof ShortcutInfo) { 378 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; 379 for (ApplicationInfo info : apps) { 380 // Added null checks to prevent NPE we've seen in the wild 381 if (dragInfo != null && 382 dragInfo.intent != null && 383 info.intent != null && 384 dragInfo.intent.getComponent().equals(info.intent.getComponent())) { 385 cancelDrag(); 386 return; 387 } 388 } 389 } 390 } 391 } 392 393 private void endDrag() { 394 if (mDragging) { 395 mDragging = false; 396 clearScrollRunnable(); 397 boolean isDeferred = false; 398 if (mDragObject.dragView != null) { 399 isDeferred = mDragObject.deferDragViewCleanupPostAnimation; 400 if (!isDeferred) { 401 mDragObject.dragView.remove(); 402 } 403 mDragObject.dragView = null; 404 } 405 406 // Only end the drag if we are not deferred 407 if (!isDeferred) { 408 for (DragListener listener : mListeners) { 409 listener.onDragEnd(); 410 } 411 } 412 } 413 414 releaseVelocityTracker(); 415 } 416 417 /** 418 * This only gets called as a result of drag view cleanup being deferred in endDrag(); 419 */ 420 void onDeferredEndDrag(DragView dragView) { 421 dragView.remove(); 422 423 // If we skipped calling onDragEnd() before, do it now 424 for (DragListener listener : mListeners) { 425 listener.onDragEnd(); 426 } 427 } 428 429 void onDeferredEndFling(DropTarget.DragObject d) { 430 d.dragSource.onFlingToDeleteCompleted(); 431 } 432 433 /** 434 * Clamps the position to the drag layer bounds. 435 */ 436 private int[] getClampedDragLayerPos(float x, float y) { 437 mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); 438 mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); 439 mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); 440 return mTmpPoint; 441 } 442 443 /** 444 * Call this from a drag source view. 445 */ 446 public boolean onInterceptTouchEvent(MotionEvent ev) { 447 if (false) { 448 Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" 449 + mDragging); 450 } 451 452 // Update the velocity tracker 453 acquireVelocityTrackerAndAddMovement(ev); 454 455 final int action = ev.getAction(); 456 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 457 final int dragLayerX = dragLayerPos[0]; 458 final int dragLayerY = dragLayerPos[1]; 459 460 switch (action) { 461 case MotionEvent.ACTION_MOVE: 462 break; 463 case MotionEvent.ACTION_DOWN: 464 // Remember location of down touch 465 mMotionDownX = dragLayerX; 466 mMotionDownY = dragLayerY; 467 mLastDropTarget = null; 468 break; 469 case MotionEvent.ACTION_UP: 470 if (mDragging) { 471 PointF vec = isFlingingToDelete(mDragObject.dragSource); 472 if (vec != null) { 473 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); 474 } else { 475 drop(dragLayerX, dragLayerY); 476 } 477 } 478 endDrag(); 479 break; 480 case MotionEvent.ACTION_CANCEL: 481 cancelDrag(); 482 break; 483 } 484 485 return mDragging; 486 } 487 488 /** 489 * Sets the view that should handle move events. 490 */ 491 void setMoveTarget(View view) { 492 mMoveTarget = view; 493 } 494 495 public boolean dispatchUnhandledMove(View focused, int direction) { 496 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 497 } 498 499 private void clearScrollRunnable() { 500 mHandler.removeCallbacks(mScrollRunnable); 501 if (mScrollState == SCROLL_WAITING_IN_ZONE) { 502 mScrollState = SCROLL_OUTSIDE_ZONE; 503 mScrollRunnable.setDirection(SCROLL_RIGHT); 504 mDragScroller.onExitScrollArea(); 505 } 506 } 507 508 private void handleMoveEvent(int x, int y) { 509 mDragObject.dragView.move(x, y); 510 511 // Drop on someone? 512 final int[] coordinates = mCoordinatesTemp; 513 DropTarget dropTarget = findDropTarget(x, y, coordinates); 514 mDragObject.x = coordinates[0]; 515 mDragObject.y = coordinates[1]; 516 if (dropTarget != null) { 517 DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); 518 if (delegate != null) { 519 dropTarget = delegate; 520 } 521 522 if (mLastDropTarget != dropTarget) { 523 if (mLastDropTarget != null) { 524 mLastDropTarget.onDragExit(mDragObject); 525 } 526 dropTarget.onDragEnter(mDragObject); 527 } 528 dropTarget.onDragOver(mDragObject); 529 } else { 530 if (mLastDropTarget != null) { 531 mLastDropTarget.onDragExit(mDragObject); 532 } 533 } 534 mLastDropTarget = dropTarget; 535 536 // After a scroll, the touch point will still be in the scroll region. 537 // Rather than scrolling immediately, require a bit of twiddling to scroll again 538 final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); 539 mDistanceSinceScroll += 540 Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); 541 mLastTouch[0] = x; 542 mLastTouch[1] = y; 543 final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; 544 545 if (x < mScrollZone) { 546 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 547 mScrollState = SCROLL_WAITING_IN_ZONE; 548 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { 549 mScrollRunnable.setDirection(SCROLL_LEFT); 550 mHandler.postDelayed(mScrollRunnable, delay); 551 } 552 } 553 } else if (x > mScrollView.getWidth() - mScrollZone) { 554 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 555 mScrollState = SCROLL_WAITING_IN_ZONE; 556 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) { 557 mScrollRunnable.setDirection(SCROLL_RIGHT); 558 mHandler.postDelayed(mScrollRunnable, delay); 559 } 560 } 561 } else { 562 clearScrollRunnable(); 563 } 564 } 565 566 public void forceMoveEvent() { 567 if (mDragging) { 568 handleMoveEvent(mDragObject.x, mDragObject.y); 569 } 570 } 571 572 /** 573 * Call this from a drag source view. 574 */ 575 public boolean onTouchEvent(MotionEvent ev) { 576 if (!mDragging) { 577 return false; 578 } 579 580 // Update the velocity tracker 581 acquireVelocityTrackerAndAddMovement(ev); 582 583 final int action = ev.getAction(); 584 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 585 final int dragLayerX = dragLayerPos[0]; 586 final int dragLayerY = dragLayerPos[1]; 587 588 switch (action) { 589 case MotionEvent.ACTION_DOWN: 590 // Remember where the motion event started 591 mMotionDownX = dragLayerX; 592 mMotionDownY = dragLayerY; 593 594 if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { 595 mScrollState = SCROLL_WAITING_IN_ZONE; 596 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 597 } else { 598 mScrollState = SCROLL_OUTSIDE_ZONE; 599 } 600 break; 601 case MotionEvent.ACTION_MOVE: 602 handleMoveEvent(dragLayerX, dragLayerY); 603 break; 604 case MotionEvent.ACTION_UP: 605 // Ensure that we've processed a move event at the current pointer location. 606 handleMoveEvent(dragLayerX, dragLayerY); 607 mHandler.removeCallbacks(mScrollRunnable); 608 609 if (mDragging) { 610 PointF vec = isFlingingToDelete(mDragObject.dragSource); 611 if (vec != null) { 612 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); 613 } else { 614 drop(dragLayerX, dragLayerY); 615 } 616 } 617 endDrag(); 618 break; 619 case MotionEvent.ACTION_CANCEL: 620 mHandler.removeCallbacks(mScrollRunnable); 621 cancelDrag(); 622 break; 623 } 624 625 return true; 626 } 627 628 /** 629 * Determines whether the user flung the current item to delete it. 630 * 631 * @return the vector at which the item was flung, or null if no fling was detected. 632 */ 633 private PointF isFlingingToDelete(DragSource source) { 634 if (mFlingToDeleteDropTarget == null) return null; 635 if (!source.supportsFlingToDelete()) return null; 636 637 ViewConfiguration config = ViewConfiguration.get(mLauncher); 638 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); 639 640 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { 641 // Do a quick dot product test to ensure that we are flinging upwards 642 PointF vel = new PointF(mVelocityTracker.getXVelocity(), 643 mVelocityTracker.getYVelocity()); 644 PointF upVec = new PointF(0f, -1f); 645 float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / 646 (vel.length() * upVec.length())); 647 if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { 648 return vel; 649 } 650 } 651 return null; 652 } 653 654 private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { 655 final int[] coordinates = mCoordinatesTemp; 656 657 mDragObject.x = coordinates[0]; 658 mDragObject.y = coordinates[1]; 659 660 // Clean up dragging on the target if it's not the current fling delete target otherwise, 661 // start dragging to it. 662 if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { 663 mLastDropTarget.onDragExit(mDragObject); 664 } 665 666 // Drop onto the fling-to-delete target 667 boolean accepted = false; 668 mFlingToDeleteDropTarget.onDragEnter(mDragObject); 669 // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for 670 // "drop" 671 mDragObject.dragComplete = true; 672 mFlingToDeleteDropTarget.onDragExit(mDragObject); 673 if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { 674 mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y, 675 vel); 676 accepted = true; 677 } 678 mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, 679 accepted); 680 } 681 682 private void drop(float x, float y) { 683 final int[] coordinates = mCoordinatesTemp; 684 final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); 685 686 mDragObject.x = coordinates[0]; 687 mDragObject.y = coordinates[1]; 688 boolean accepted = false; 689 if (dropTarget != null) { 690 mDragObject.dragComplete = true; 691 dropTarget.onDragExit(mDragObject); 692 if (dropTarget.acceptDrop(mDragObject)) { 693 dropTarget.onDrop(mDragObject); 694 accepted = true; 695 } 696 } 697 mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); 698 } 699 700 private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { 701 final Rect r = mRectTemp; 702 703 final ArrayList<DropTarget> dropTargets = mDropTargets; 704 final int count = dropTargets.size(); 705 for (int i=count-1; i>=0; i--) { 706 DropTarget target = dropTargets.get(i); 707 if (!target.isDropEnabled()) 708 continue; 709 710 target.getHitRect(r); 711 712 // Convert the hit rect to DragLayer coordinates 713 target.getLocationInDragLayer(dropCoordinates); 714 r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); 715 716 mDragObject.x = x; 717 mDragObject.y = y; 718 if (r.contains(x, y)) { 719 DropTarget delegate = target.getDropTargetDelegate(mDragObject); 720 if (delegate != null) { 721 target = delegate; 722 target.getLocationInDragLayer(dropCoordinates); 723 } 724 725 // Make dropCoordinates relative to the DropTarget 726 dropCoordinates[0] = x - dropCoordinates[0]; 727 dropCoordinates[1] = y - dropCoordinates[1]; 728 729 return target; 730 } 731 } 732 return null; 733 } 734 735 public void setDragScoller(DragScroller scroller) { 736 mDragScroller = scroller; 737 } 738 739 public void setWindowToken(IBinder token) { 740 mWindowToken = token; 741 } 742 743 /** 744 * Sets the drag listner which will be notified when a drag starts or ends. 745 */ 746 public void addDragListener(DragListener l) { 747 mListeners.add(l); 748 } 749 750 /** 751 * Remove a previously installed drag listener. 752 */ 753 public void removeDragListener(DragListener l) { 754 mListeners.remove(l); 755 } 756 757 /** 758 * Add a DropTarget to the list of potential places to receive drop events. 759 */ 760 public void addDropTarget(DropTarget target) { 761 mDropTargets.add(target); 762 } 763 764 /** 765 * Don't send drop events to <em>target</em> any more. 766 */ 767 public void removeDropTarget(DropTarget target) { 768 mDropTargets.remove(target); 769 } 770 771 /** 772 * Sets the current fling-to-delete drop target. 773 */ 774 public void setFlingToDeleteDropTarget(DropTarget target) { 775 mFlingToDeleteDropTarget = target; 776 } 777 778 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 779 if (mVelocityTracker == null) { 780 mVelocityTracker = VelocityTracker.obtain(); 781 } 782 mVelocityTracker.addMovement(ev); 783 } 784 785 private void releaseVelocityTracker() { 786 if (mVelocityTracker != null) { 787 mVelocityTracker.recycle(); 788 mVelocityTracker = null; 789 } 790 } 791 792 /** 793 * Set which view scrolls for touch events near the edge of the screen. 794 */ 795 public void setScrollView(View v) { 796 mScrollView = v; 797 } 798 799 DragView getDragView() { 800 return mDragObject.dragView; 801 } 802 803 private class ScrollRunnable implements Runnable { 804 private int mDirection; 805 806 ScrollRunnable() { 807 } 808 809 public void run() { 810 if (mDragScroller != null) { 811 if (mDirection == SCROLL_LEFT) { 812 mDragScroller.scrollLeft(); 813 } else { 814 mDragScroller.scrollRight(); 815 } 816 mScrollState = SCROLL_OUTSIDE_ZONE; 817 mDistanceSinceScroll = 0; 818 mDragScroller.onExitScrollArea(); 819 820 if (isDragging()) { 821 // Force an update so that we can requeue the scroller if necessary 822 forceMoveEvent(); 823 } 824 } 825 } 826 827 void setDirection(int direction) { 828 mDirection = direction; 829 } 830 } 831} 832