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