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