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