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