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