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