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