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