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