DragLayer.java revision 11c45f114165330ea9c895ca6fdf83fdaa2e5fad
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.launcher3.dragndrop; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.TimeInterpolator; 22import android.animation.ValueAnimator; 23import android.animation.ValueAnimator.AnimatorUpdateListener; 24import android.content.Context; 25import android.content.res.Resources; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.Rect; 29import android.graphics.drawable.Drawable; 30import android.util.AttributeSet; 31import android.view.DragEvent; 32import android.view.KeyEvent; 33import android.view.MotionEvent; 34import android.view.View; 35import android.view.ViewGroup; 36import android.view.accessibility.AccessibilityEvent; 37import android.view.accessibility.AccessibilityManager; 38import android.view.animation.DecelerateInterpolator; 39import android.view.animation.Interpolator; 40import android.widget.FrameLayout; 41import android.widget.TextView; 42 43import com.android.launcher3.AppWidgetResizeFrame; 44import com.android.launcher3.CellLayout; 45import com.android.launcher3.Folder; 46import com.android.launcher3.FolderIcon; 47import com.android.launcher3.InsettableFrameLayout; 48import com.android.launcher3.ItemInfo; 49import com.android.launcher3.Launcher; 50import com.android.launcher3.LauncherAppState; 51import com.android.launcher3.LauncherAppWidgetHostView; 52import com.android.launcher3.SearchDropTargetBar; 53import com.android.launcher3.ShortcutAndWidgetContainer; 54import com.android.launcher3.Utilities; 55import com.android.launcher3.Workspace; 56import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; 57import com.android.launcher3.util.Thunk; 58 59import com.android.launcher3.R; 60 61import java.util.ArrayList; 62 63/** 64 * A ViewGroup that coordinates dragging across its descendants 65 */ 66public class DragLayer extends InsettableFrameLayout { 67 68 public static final int ANIMATION_END_DISAPPEAR = 0; 69 public static final int ANIMATION_END_REMAIN_VISIBLE = 2; 70 71 // Scrim color without any alpha component. 72 private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF; 73 74 private final int[] mTmpXY = new int[2]; 75 76 @Thunk DragController mDragController; 77 78 private int mXDown, mYDown; 79 private Launcher mLauncher; 80 81 // Variables relating to resizing widgets 82 private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>(); 83 private final boolean mIsRtl; 84 private AppWidgetResizeFrame mCurrentResizeFrame; 85 86 // Variables relating to animation of views after drop 87 private ValueAnimator mDropAnim = null; 88 private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 89 @Thunk DragView mDropView = null; 90 @Thunk int mAnchorViewInitialScrollX = 0; 91 @Thunk View mAnchorView = null; 92 93 private boolean mHoverPointClosesFolder = false; 94 private final Rect mHitRect = new Rect(); 95 96 private TouchCompleteListener mTouchCompleteListener; 97 98 private View mOverlayView; 99 private int mTopViewIndex; 100 private int mChildCountOnLastUpdate = -1; 101 102 // Darkening scrim 103 private float mBackgroundAlpha = 0; 104 105 private boolean mBlockTouches = false; 106 107 /** 108 * Used to create a new DragLayer from XML. 109 * 110 * @param context The application's context. 111 * @param attrs The attributes set containing the Workspace's customization values. 112 */ 113 public DragLayer(Context context, AttributeSet attrs) { 114 super(context, attrs); 115 116 // Disable multitouch across the workspace/all apps/customize tray 117 setMotionEventSplittingEnabled(false); 118 setChildrenDrawingOrderEnabled(true); 119 120 mIsRtl = Utilities.isRtl(getResources()); 121 } 122 123 public void setup(Launcher launcher, DragController controller) { 124 mLauncher = launcher; 125 mDragController = controller; 126 } 127 128 @Override 129 public boolean dispatchKeyEvent(KeyEvent event) { 130 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 131 } 132 133 public void showOverlayView(View overlayView) { 134 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 135 mOverlayView = overlayView; 136 addView(overlayView, lp); 137 138 // ensure that the overlay view stays on top. we can't use drawing order for this 139 // because in API level 16 touch dispatch doesn't respect drawing order. 140 mOverlayView.bringToFront(); 141 } 142 143 public void dismissOverlayView() { 144 removeView(mOverlayView); 145 } 146 147 private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { 148 getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); 149 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 150 return true; 151 } 152 return false; 153 } 154 155 private boolean isEventOverFolder(Folder folder, MotionEvent ev) { 156 getDescendantRectRelativeToSelf(folder, mHitRect); 157 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 158 return true; 159 } 160 return false; 161 } 162 163 private boolean isEventOverDropTargetBar(MotionEvent ev) { 164 getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect); 165 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 166 return true; 167 } 168 169 getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect); 170 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 171 return true; 172 } 173 return false; 174 } 175 176 public void setBlockTouch(boolean block) { 177 mBlockTouches = block; 178 } 179 180 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 181 Rect hitRect = new Rect(); 182 int x = (int) ev.getX(); 183 int y = (int) ev.getY(); 184 185 if (mBlockTouches) { 186 return true; 187 } 188 189 for (AppWidgetResizeFrame child: mResizeFrames) { 190 child.getHitRect(hitRect); 191 if (hitRect.contains(x, y)) { 192 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 193 mCurrentResizeFrame = child; 194 mXDown = x; 195 mYDown = y; 196 requestDisallowInterceptTouchEvent(true); 197 return true; 198 } 199 } 200 } 201 202 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 203 if (currentFolder != null && intercept) { 204 if (currentFolder.isEditingName()) { 205 if (!isEventOverFolderTextRegion(currentFolder, ev)) { 206 currentFolder.dismissEditingName(); 207 return true; 208 } 209 } 210 211 if (!isEventOverFolder(currentFolder, ev)) { 212 if (isInAccessibleDrag()) { 213 // Do not close the folder if in drag and drop. 214 if (!isEventOverDropTargetBar(ev)) { 215 return true; 216 } 217 } else { 218 mLauncher.closeFolder(); 219 return true; 220 } 221 } 222 } 223 return false; 224 } 225 226 @Override 227 public boolean onInterceptTouchEvent(MotionEvent ev) { 228 int action = ev.getAction(); 229 230 if (action == MotionEvent.ACTION_DOWN) { 231 if (handleTouchDown(ev, true)) { 232 return true; 233 } 234 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 235 if (mTouchCompleteListener != null) { 236 mTouchCompleteListener.onTouchComplete(); 237 } 238 mTouchCompleteListener = null; 239 } 240 clearAllResizeFrames(); 241 return mDragController.onInterceptTouchEvent(ev); 242 } 243 244 @Override 245 public boolean onInterceptHoverEvent(MotionEvent ev) { 246 if (mLauncher == null || mLauncher.getWorkspace() == null) { 247 return false; 248 } 249 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 250 if (currentFolder == null) { 251 return false; 252 } else { 253 AccessibilityManager accessibilityManager = (AccessibilityManager) 254 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 255 if (accessibilityManager.isTouchExplorationEnabled()) { 256 final int action = ev.getAction(); 257 boolean isOverFolderOrSearchBar; 258 switch (action) { 259 case MotionEvent.ACTION_HOVER_ENTER: 260 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) || 261 (isInAccessibleDrag() && isEventOverDropTargetBar(ev)); 262 if (!isOverFolderOrSearchBar) { 263 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 264 mHoverPointClosesFolder = true; 265 return true; 266 } 267 mHoverPointClosesFolder = false; 268 break; 269 case MotionEvent.ACTION_HOVER_MOVE: 270 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) || 271 (isInAccessibleDrag() && isEventOverDropTargetBar(ev)); 272 if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) { 273 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 274 mHoverPointClosesFolder = true; 275 return true; 276 } else if (!isOverFolderOrSearchBar) { 277 return true; 278 } 279 mHoverPointClosesFolder = false; 280 } 281 } 282 } 283 return false; 284 } 285 286 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { 287 AccessibilityManager accessibilityManager = (AccessibilityManager) 288 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 289 if (accessibilityManager.isEnabled()) { 290 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; 291 AccessibilityEvent event = AccessibilityEvent.obtain( 292 AccessibilityEvent.TYPE_VIEW_FOCUSED); 293 onInitializeAccessibilityEvent(event); 294 event.getText().add(getContext().getString(stringId)); 295 accessibilityManager.sendAccessibilityEvent(event); 296 } 297 } 298 299 private boolean isInAccessibleDrag() { 300 LauncherAccessibilityDelegate delegate = LauncherAppState 301 .getInstance().getAccessibilityDelegate(); 302 return delegate != null && delegate.isInAccessibleDrag(); 303 } 304 305 @Override 306 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 307 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 308 if (currentFolder != null) { 309 if (child == currentFolder) { 310 return super.onRequestSendAccessibilityEvent(child, event); 311 } 312 313 if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) { 314 return super.onRequestSendAccessibilityEvent(child, event); 315 } 316 // Skip propagating onRequestSendAccessibilityEvent all for other children 317 // when a folder is open 318 return false; 319 } 320 return super.onRequestSendAccessibilityEvent(child, event); 321 } 322 323 @Override 324 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { 325 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 326 if (currentFolder != null) { 327 // Only add the folder as a child for accessibility when it is open 328 childrenForAccessibility.add(currentFolder); 329 330 if (isInAccessibleDrag()) { 331 childrenForAccessibility.add(mLauncher.getSearchDropTargetBar()); 332 childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar()); 333 } 334 } else { 335 super.addChildrenForAccessibility(childrenForAccessibility); 336 } 337 } 338 339 @Override 340 public boolean onHoverEvent(MotionEvent ev) { 341 // If we've received this, we've already done the necessary handling 342 // in onInterceptHoverEvent. Return true to consume the event. 343 return false; 344 } 345 346 @Override 347 public boolean onTouchEvent(MotionEvent ev) { 348 boolean handled = false; 349 int action = ev.getAction(); 350 351 int x = (int) ev.getX(); 352 int y = (int) ev.getY(); 353 354 if (mBlockTouches) { 355 return true; 356 } 357 358 if (action == MotionEvent.ACTION_DOWN) { 359 if (handleTouchDown(ev, false)) { 360 return true; 361 } 362 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 363 if (mTouchCompleteListener != null) { 364 mTouchCompleteListener.onTouchComplete(); 365 } 366 mTouchCompleteListener = null; 367 } 368 369 if (mCurrentResizeFrame != null) { 370 handled = true; 371 switch (action) { 372 case MotionEvent.ACTION_MOVE: 373 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 374 break; 375 case MotionEvent.ACTION_CANCEL: 376 case MotionEvent.ACTION_UP: 377 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 378 mCurrentResizeFrame.onTouchUp(); 379 mCurrentResizeFrame = null; 380 } 381 } 382 if (handled) return true; 383 return mDragController.onTouchEvent(ev); 384 } 385 386 @Override 387 public boolean onDragEvent (DragEvent event) { 388 return mDragController.onDragEvent(event); 389 } 390 391 /** 392 * Determine the rect of the descendant in this DragLayer's coordinates 393 * 394 * @param descendant The descendant whose coordinates we want to find. 395 * @param r The rect into which to place the results. 396 * @return The factor by which this descendant is scaled relative to this DragLayer. 397 */ 398 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 399 mTmpXY[0] = 0; 400 mTmpXY[1] = 0; 401 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 402 403 r.set(mTmpXY[0], mTmpXY[1], 404 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()), 405 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight())); 406 return scale; 407 } 408 409 public float getLocationInDragLayer(View child, int[] loc) { 410 loc[0] = 0; 411 loc[1] = 0; 412 return getDescendantCoordRelativeToSelf(child, loc); 413 } 414 415 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 416 return getDescendantCoordRelativeToSelf(descendant, coord, false); 417 } 418 419 /** 420 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 421 * coordinates. 422 * 423 * @param descendant The descendant to which the passed coordinate is relative. 424 * @param coord The coordinate that we want mapped. 425 * @param includeRootScroll Whether or not to account for the scroll of the root descendant: 426 * sometimes this is relevant as in a child's coordinates within the root descendant. 427 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 428 * this scale factor is assumed to be equal in X and Y, and so if at any point this 429 * assumption fails, we will need to return a pair of scale factors. 430 */ 431 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord, 432 boolean includeRootScroll) { 433 return Utilities.getDescendantCoordRelativeToParent(descendant, this, 434 coord, includeRootScroll); 435 } 436 437 /** 438 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. 439 */ 440 public float mapCoordInSelfToDescendent(View descendant, int[] coord) { 441 return Utilities.mapCoordInSelfToDescendent(descendant, this, coord); 442 } 443 444 public void getViewRectRelativeToSelf(View v, Rect r) { 445 int[] loc = new int[2]; 446 getLocationInWindow(loc); 447 int x = loc[0]; 448 int y = loc[1]; 449 450 v.getLocationInWindow(loc); 451 int vX = loc[0]; 452 int vY = loc[1]; 453 454 int left = vX - x; 455 int top = vY - y; 456 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 457 } 458 459 @Override 460 public boolean dispatchUnhandledMove(View focused, int direction) { 461 return mDragController.dispatchUnhandledMove(focused, direction); 462 } 463 464 @Override 465 public LayoutParams generateLayoutParams(AttributeSet attrs) { 466 return new LayoutParams(getContext(), attrs); 467 } 468 469 @Override 470 protected LayoutParams generateDefaultLayoutParams() { 471 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 472 } 473 474 // Override to allow type-checking of LayoutParams. 475 @Override 476 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 477 return p instanceof LayoutParams; 478 } 479 480 @Override 481 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 482 return new LayoutParams(p); 483 } 484 485 public static class LayoutParams extends InsettableFrameLayout.LayoutParams { 486 public int x, y; 487 public boolean customPosition = false; 488 489 public LayoutParams(Context c, AttributeSet attrs) { 490 super(c, attrs); 491 } 492 493 public LayoutParams(int width, int height) { 494 super(width, height); 495 } 496 497 public LayoutParams(ViewGroup.LayoutParams lp) { 498 super(lp); 499 } 500 501 public void setWidth(int width) { 502 this.width = width; 503 } 504 505 public int getWidth() { 506 return width; 507 } 508 509 public void setHeight(int height) { 510 this.height = height; 511 } 512 513 public int getHeight() { 514 return height; 515 } 516 517 public void setX(int x) { 518 this.x = x; 519 } 520 521 public int getX() { 522 return x; 523 } 524 525 public void setY(int y) { 526 this.y = y; 527 } 528 529 public int getY() { 530 return y; 531 } 532 } 533 534 protected void onLayout(boolean changed, int l, int t, int r, int b) { 535 super.onLayout(changed, l, t, r, b); 536 int count = getChildCount(); 537 for (int i = 0; i < count; i++) { 538 View child = getChildAt(i); 539 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 540 if (flp instanceof LayoutParams) { 541 final LayoutParams lp = (LayoutParams) flp; 542 if (lp.customPosition) { 543 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 544 } 545 } 546 } 547 } 548 549 public void clearAllResizeFrames() { 550 if (mResizeFrames.size() > 0) { 551 for (AppWidgetResizeFrame frame: mResizeFrames) { 552 frame.commitResize(); 553 removeView(frame); 554 } 555 mResizeFrames.clear(); 556 } 557 } 558 559 public boolean hasResizeFrames() { 560 return mResizeFrames.size() > 0; 561 } 562 563 public boolean isWidgetBeingResized() { 564 return mCurrentResizeFrame != null; 565 } 566 567 public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, 568 CellLayout cellLayout) { 569 AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), 570 widget, cellLayout, this); 571 572 LayoutParams lp = new LayoutParams(-1, -1); 573 lp.customPosition = true; 574 575 addView(resizeFrame, lp); 576 mResizeFrames.add(resizeFrame); 577 578 resizeFrame.snapToWidget(false); 579 } 580 581 public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, 582 float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, 583 int duration) { 584 Rect r = new Rect(); 585 getViewRectRelativeToSelf(dragView, r); 586 final int fromX = r.left; 587 final int fromY = r.top; 588 589 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, 590 onFinishRunnable, animationEndStyle, duration, null); 591 } 592 593 public void animateViewIntoPosition(DragView dragView, final View child, 594 final Runnable onFinishAnimationRunnable, View anchorView) { 595 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView); 596 } 597 598 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 599 final Runnable onFinishAnimationRunnable, View anchorView) { 600 ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); 601 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 602 parentChildren.measureChild(child); 603 604 Rect r = new Rect(); 605 getViewRectRelativeToSelf(dragView, r); 606 607 int coord[] = new int[2]; 608 float childScale = child.getScaleX(); 609 coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2); 610 coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2); 611 612 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 613 // the correct coordinates (above) and use these to determine the final location 614 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 615 // We need to account for the scale of the child itself, as the above only accounts for 616 // for the scale in parents. 617 scale *= childScale; 618 int toX = coord[0]; 619 int toY = coord[1]; 620 float toScale = scale; 621 if (child instanceof TextView) { 622 TextView tv = (TextView) child; 623 // Account for the source scale of the icon (ie. from AllApps to Workspace, in which 624 // the workspace may have smaller icon bounds). 625 toScale = scale / dragView.getIntrinsicIconScaleFactor(); 626 627 // The child may be scaled (always about the center of the view) so to account for it, 628 // we have to offset the position by the scaled size. Once we do that, we can center 629 // the drag view about the scaled child view. 630 toY += Math.round(toScale * tv.getPaddingTop()); 631 toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; 632 if (dragView.getDragVisualizeOffset() != null) { 633 toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); 634 } 635 636 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 637 } else if (child instanceof FolderIcon) { 638 // Account for holographic blur padding on the drag view 639 toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop())); 640 toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2; 641 toY -= (1 - scale) * dragView.getMeasuredHeight() / 2; 642 // Center in the x coordinate about the target's drawable 643 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 644 } else { 645 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 646 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 647 - child.getMeasuredWidth()))) / 2; 648 } 649 650 final int fromX = r.left; 651 final int fromY = r.top; 652 child.setVisibility(INVISIBLE); 653 Runnable onCompleteRunnable = new Runnable() { 654 public void run() { 655 child.setVisibility(VISIBLE); 656 if (onFinishAnimationRunnable != null) { 657 onFinishAnimationRunnable.run(); 658 } 659 } 660 }; 661 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, 662 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); 663 } 664 665 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, 666 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, 667 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, 668 int animationEndStyle, int duration, View anchorView) { 669 Rect from = new Rect(fromX, fromY, fromX + 670 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 671 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 672 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, 673 null, null, onCompleteRunnable, animationEndStyle, anchorView); 674 } 675 676 /** 677 * This method animates a view at the end of a drag and drop animation. 678 * 679 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 680 * doesn't need to be a child of DragLayer. 681 * @param from The initial location of the view. Only the left and top parameters are used. 682 * @param to The final location of the view. Only the left and top parameters are used. This 683 * location doesn't account for scaling, and so should be centered about the desired 684 * final location (including scaling). 685 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 686 * @param finalScale The final scale of the view. The view is scaled about its center. 687 * @param duration The duration of the animation. 688 * @param motionInterpolator The interpolator to use for the location of the view. 689 * @param alphaInterpolator The interpolator to use for the alpha of the view. 690 * @param onCompleteRunnable Optional runnable to run on animation completion. 691 * @param fadeOut Whether or not to fade out the view once the animation completes. If true, 692 * the runnable will execute after the view is faded out. 693 * @param anchorView If not null, this represents the view which the animated view stays 694 * anchored to in case scrolling is currently taking place. Note: currently this is 695 * only used for the X dimension for the case of the workspace. 696 */ 697 public void animateView(final DragView view, final Rect from, final Rect to, 698 final float finalAlpha, final float initScaleX, final float initScaleY, 699 final float finalScaleX, final float finalScaleY, int duration, 700 final Interpolator motionInterpolator, final Interpolator alphaInterpolator, 701 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { 702 703 // Calculate the duration of the animation based on the object's distance 704 final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top); 705 final Resources res = getResources(); 706 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 707 708 // If duration < 0, this is a cue to compute the duration based on the distance 709 if (duration < 0) { 710 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 711 if (dist < maxDist) { 712 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 713 } 714 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); 715 } 716 717 // Fall back to cubic ease out interpolator for the animation if none is specified 718 TimeInterpolator interpolator = null; 719 if (alphaInterpolator == null || motionInterpolator == null) { 720 interpolator = mCubicEaseOutInterpolator; 721 } 722 723 // Animate the view 724 final float initAlpha = view.getAlpha(); 725 final float dropViewScale = view.getScaleX(); 726 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { 727 @Override 728 public void onAnimationUpdate(ValueAnimator animation) { 729 final float percent = (Float) animation.getAnimatedValue(); 730 final int width = view.getMeasuredWidth(); 731 final int height = view.getMeasuredHeight(); 732 733 float alphaPercent = alphaInterpolator == null ? percent : 734 alphaInterpolator.getInterpolation(percent); 735 float motionPercent = motionInterpolator == null ? percent : 736 motionInterpolator.getInterpolation(percent); 737 738 float initialScaleX = initScaleX * dropViewScale; 739 float initialScaleY = initScaleY * dropViewScale; 740 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); 741 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); 742 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); 743 744 float fromLeft = from.left + (initialScaleX - 1f) * width / 2; 745 float fromTop = from.top + (initialScaleY - 1f) * height / 2; 746 747 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); 748 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); 749 750 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() * 751 (mAnchorViewInitialScrollX - mAnchorView.getScrollX())); 752 753 int xPos = x - mDropView.getScrollX() + anchorAdjust; 754 int yPos = y - mDropView.getScrollY(); 755 756 mDropView.setTranslationX(xPos); 757 mDropView.setTranslationY(yPos); 758 mDropView.setScaleX(scaleX); 759 mDropView.setScaleY(scaleY); 760 mDropView.setAlpha(alpha); 761 } 762 }; 763 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, 764 anchorView); 765 } 766 767 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, 768 TimeInterpolator interpolator, final Runnable onCompleteRunnable, 769 final int animationEndStyle, View anchorView) { 770 // Clean up the previous animations 771 if (mDropAnim != null) mDropAnim.cancel(); 772 773 // Show the drop view if it was previously hidden 774 mDropView = view; 775 mDropView.cancelAnimation(); 776 mDropView.requestLayout(); 777 778 // Set the anchor view if the page is scrolling 779 if (anchorView != null) { 780 mAnchorViewInitialScrollX = anchorView.getScrollX(); 781 } 782 mAnchorView = anchorView; 783 784 // Create and start the animation 785 mDropAnim = new ValueAnimator(); 786 mDropAnim.setInterpolator(interpolator); 787 mDropAnim.setDuration(duration); 788 mDropAnim.setFloatValues(0f, 1f); 789 mDropAnim.addUpdateListener(updateCb); 790 mDropAnim.addListener(new AnimatorListenerAdapter() { 791 public void onAnimationEnd(Animator animation) { 792 if (onCompleteRunnable != null) { 793 onCompleteRunnable.run(); 794 } 795 switch (animationEndStyle) { 796 case ANIMATION_END_DISAPPEAR: 797 clearAnimatedView(); 798 break; 799 case ANIMATION_END_REMAIN_VISIBLE: 800 break; 801 } 802 } 803 }); 804 mDropAnim.start(); 805 } 806 807 public void clearAnimatedView() { 808 if (mDropAnim != null) { 809 mDropAnim.cancel(); 810 } 811 if (mDropView != null) { 812 mDragController.onDeferredEndDrag(mDropView); 813 } 814 mDropView = null; 815 invalidate(); 816 } 817 818 public View getAnimatedView() { 819 return mDropView; 820 } 821 822 @Override 823 public void onChildViewAdded(View parent, View child) { 824 super.onChildViewAdded(parent, child); 825 if (mOverlayView != null) { 826 // ensure that the overlay view stays on top. we can't use drawing order for this 827 // because in API level 16 touch dispatch doesn't respect drawing order. 828 mOverlayView.bringToFront(); 829 } 830 updateChildIndices(); 831 } 832 833 @Override 834 public void onChildViewRemoved(View parent, View child) { 835 updateChildIndices(); 836 } 837 838 @Override 839 public void bringChildToFront(View child) { 840 super.bringChildToFront(child); 841 if (child != mOverlayView && mOverlayView != null) { 842 // ensure that the overlay view stays on top. we can't use drawing order for this 843 // because in API level 16 touch dispatch doesn't respect drawing order. 844 mOverlayView.bringToFront(); 845 } 846 updateChildIndices(); 847 } 848 849 private void updateChildIndices() { 850 mTopViewIndex = -1; 851 int childCount = getChildCount(); 852 for (int i = 0; i < childCount; i++) { 853 if (getChildAt(i) instanceof DragView) { 854 mTopViewIndex = i; 855 } 856 } 857 mChildCountOnLastUpdate = childCount; 858 } 859 860 @Override 861 protected int getChildDrawingOrder(int childCount, int i) { 862 if (mChildCountOnLastUpdate != childCount) { 863 // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed. 864 // Pre-18, the child was not added / removed by the time of those callbacks. We need to 865 // force update our representation of things here to avoid crashing on pre-18 devices 866 // in certain instances. 867 updateChildIndices(); 868 } 869 870 // i represents the current draw iteration 871 if (mTopViewIndex == -1) { 872 // in general we do nothing 873 return i; 874 } else if (i == childCount - 1) { 875 // if we have a top index, we return it when drawing last item (highest z-order) 876 return mTopViewIndex; 877 } else if (i < mTopViewIndex) { 878 return i; 879 } else { 880 // for indexes greater than the top index, we fetch one item above to shift for the 881 // displacement of the top index 882 return i + 1; 883 } 884 } 885 886 @Override 887 protected void dispatchDraw(Canvas canvas) { 888 // Draw the background below children. 889 if (mBackgroundAlpha > 0.0f) { 890 int alpha = (int) (mBackgroundAlpha * 255); 891 canvas.drawColor((alpha << 24) | SCRIM_COLOR); 892 } 893 894 super.dispatchDraw(canvas); 895 } 896 897 public void setBackgroundAlpha(float alpha) { 898 if (alpha != mBackgroundAlpha) { 899 mBackgroundAlpha = alpha; 900 invalidate(); 901 } 902 } 903 904 public float getBackgroundAlpha() { 905 return mBackgroundAlpha; 906 } 907 908 public void setTouchCompleteListener(TouchCompleteListener listener) { 909 mTouchCompleteListener = listener; 910 } 911 912 public interface TouchCompleteListener { 913 public void onTouchComplete(); 914 } 915} 916