DragLayer.java revision ff4f201fadb131e7c367f64296e3a08090946ada
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 (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 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 mCurrentResizeFrame.commitResize(); 548 removeView(mCurrentResizeFrame); 549 mCurrentResizeFrame = null; 550 } 551 } 552 553 public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) { 554 clearResizeFrame(); 555 556 mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher) 557 .inflate(R.layout.app_widget_resize_frame, this, false); 558 mCurrentResizeFrame.setupForWidget(widget, cellLayout, this); 559 ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true; 560 561 addView(mCurrentResizeFrame); 562 mCurrentResizeFrame.snapToWidget(false); 563 } 564 565 public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, 566 float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, 567 int duration) { 568 Rect r = new Rect(); 569 getViewRectRelativeToSelf(dragView, r); 570 final int fromX = r.left; 571 final int fromY = r.top; 572 573 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, 574 onFinishRunnable, animationEndStyle, duration, null); 575 } 576 577 public void animateViewIntoPosition(DragView dragView, final View child, 578 final Runnable onFinishAnimationRunnable, View anchorView) { 579 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView); 580 } 581 582 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 583 final Runnable onFinishAnimationRunnable, View anchorView) { 584 ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); 585 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 586 parentChildren.measureChild(child); 587 588 Rect r = new Rect(); 589 getViewRectRelativeToSelf(dragView, r); 590 591 int coord[] = new int[2]; 592 float childScale = child.getScaleX(); 593 coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2); 594 coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2); 595 596 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 597 // the correct coordinates (above) and use these to determine the final location 598 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 599 // We need to account for the scale of the child itself, as the above only accounts for 600 // for the scale in parents. 601 scale *= childScale; 602 int toX = coord[0]; 603 int toY = coord[1]; 604 float toScale = scale; 605 if (child instanceof TextView) { 606 TextView tv = (TextView) child; 607 // Account for the source scale of the icon (ie. from AllApps to Workspace, in which 608 // the workspace may have smaller icon bounds). 609 toScale = scale / dragView.getIntrinsicIconScaleFactor(); 610 611 // The child may be scaled (always about the center of the view) so to account for it, 612 // we have to offset the position by the scaled size. Once we do that, we can center 613 // the drag view about the scaled child view. 614 toY += Math.round(toScale * tv.getPaddingTop()); 615 toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; 616 if (dragView.getDragVisualizeOffset() != null) { 617 toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); 618 } 619 620 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 621 } else if (child instanceof FolderIcon) { 622 // Account for holographic blur padding on the drag view 623 toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop())); 624 toY -= scale * dragView.getBlurSizeOutline() / 2; 625 toY -= (1 - scale) * dragView.getMeasuredHeight() / 2; 626 // Center in the x coordinate about the target's drawable 627 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 628 } else { 629 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 630 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 631 - child.getMeasuredWidth()))) / 2; 632 } 633 634 final int fromX = r.left; 635 final int fromY = r.top; 636 child.setVisibility(INVISIBLE); 637 Runnable onCompleteRunnable = new Runnable() { 638 public void run() { 639 child.setVisibility(VISIBLE); 640 if (onFinishAnimationRunnable != null) { 641 onFinishAnimationRunnable.run(); 642 } 643 } 644 }; 645 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, 646 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); 647 } 648 649 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, 650 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, 651 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, 652 int animationEndStyle, int duration, View anchorView) { 653 Rect from = new Rect(fromX, fromY, fromX + 654 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 655 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 656 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, 657 null, null, onCompleteRunnable, animationEndStyle, anchorView); 658 } 659 660 /** 661 * This method animates a view at the end of a drag and drop animation. 662 * 663 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 664 * doesn't need to be a child of DragLayer. 665 * @param from The initial location of the view. Only the left and top parameters are used. 666 * @param to The final location of the view. Only the left and top parameters are used. This 667 * location doesn't account for scaling, and so should be centered about the desired 668 * final location (including scaling). 669 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 670 * @param finalScaleX The final scale of the view. The view is scaled about its center. 671 * @param finalScaleY The final scale of the view. The view is scaled about its center. 672 * @param duration The duration of the animation. 673 * @param motionInterpolator The interpolator to use for the location of the view. 674 * @param alphaInterpolator The interpolator to use for the alpha of the view. 675 * @param onCompleteRunnable Optional runnable to run on animation completion. 676 * @param animationEndStyle Whether or not to fade out the view once the animation completes. 677 * {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}. 678 * @param anchorView If not null, this represents the view which the animated view stays 679 * anchored to in case scrolling is currently taking place. Note: currently this is 680 * only used for the X dimension for the case of the workspace. 681 */ 682 public void animateView(final DragView view, final Rect from, final Rect to, 683 final float finalAlpha, final float initScaleX, final float initScaleY, 684 final float finalScaleX, final float finalScaleY, int duration, 685 final Interpolator motionInterpolator, final Interpolator alphaInterpolator, 686 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { 687 688 // Calculate the duration of the animation based on the object's distance 689 final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top); 690 final Resources res = getResources(); 691 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 692 693 // If duration < 0, this is a cue to compute the duration based on the distance 694 if (duration < 0) { 695 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 696 if (dist < maxDist) { 697 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 698 } 699 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); 700 } 701 702 // Fall back to cubic ease out interpolator for the animation if none is specified 703 TimeInterpolator interpolator = null; 704 if (alphaInterpolator == null || motionInterpolator == null) { 705 interpolator = mCubicEaseOutInterpolator; 706 } 707 708 // Animate the view 709 final float initAlpha = view.getAlpha(); 710 final float dropViewScale = view.getScaleX(); 711 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { 712 @Override 713 public void onAnimationUpdate(ValueAnimator animation) { 714 final float percent = (Float) animation.getAnimatedValue(); 715 final int width = view.getMeasuredWidth(); 716 final int height = view.getMeasuredHeight(); 717 718 float alphaPercent = alphaInterpolator == null ? percent : 719 alphaInterpolator.getInterpolation(percent); 720 float motionPercent = motionInterpolator == null ? percent : 721 motionInterpolator.getInterpolation(percent); 722 723 float initialScaleX = initScaleX * dropViewScale; 724 float initialScaleY = initScaleY * dropViewScale; 725 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); 726 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); 727 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); 728 729 float fromLeft = from.left + (initialScaleX - 1f) * width / 2; 730 float fromTop = from.top + (initialScaleY - 1f) * height / 2; 731 732 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); 733 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); 734 735 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() * 736 (mAnchorViewInitialScrollX - mAnchorView.getScrollX())); 737 738 int xPos = x - mDropView.getScrollX() + anchorAdjust; 739 int yPos = y - mDropView.getScrollY(); 740 741 mDropView.setTranslationX(xPos); 742 mDropView.setTranslationY(yPos); 743 mDropView.setScaleX(scaleX); 744 mDropView.setScaleY(scaleY); 745 mDropView.setAlpha(alpha); 746 } 747 }; 748 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, 749 anchorView); 750 } 751 752 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, 753 TimeInterpolator interpolator, final Runnable onCompleteRunnable, 754 final int animationEndStyle, View anchorView) { 755 // Clean up the previous animations 756 if (mDropAnim != null) mDropAnim.cancel(); 757 758 // Show the drop view if it was previously hidden 759 mDropView = view; 760 mDropView.cancelAnimation(); 761 mDropView.requestLayout(); 762 763 // Set the anchor view if the page is scrolling 764 if (anchorView != null) { 765 mAnchorViewInitialScrollX = anchorView.getScrollX(); 766 } 767 mAnchorView = anchorView; 768 769 // Create and start the animation 770 mDropAnim = new ValueAnimator(); 771 mDropAnim.setInterpolator(interpolator); 772 mDropAnim.setDuration(duration); 773 mDropAnim.setFloatValues(0f, 1f); 774 mDropAnim.addUpdateListener(updateCb); 775 mDropAnim.addListener(new AnimatorListenerAdapter() { 776 public void onAnimationEnd(Animator animation) { 777 if (onCompleteRunnable != null) { 778 onCompleteRunnable.run(); 779 } 780 switch (animationEndStyle) { 781 case ANIMATION_END_DISAPPEAR: 782 clearAnimatedView(); 783 break; 784 case ANIMATION_END_REMAIN_VISIBLE: 785 break; 786 } 787 } 788 }); 789 mDropAnim.start(); 790 } 791 792 public void clearAnimatedView() { 793 if (mDropAnim != null) { 794 mDropAnim.cancel(); 795 } 796 if (mDropView != null) { 797 mDragController.onDeferredEndDrag(mDropView); 798 } 799 mDropView = null; 800 invalidate(); 801 } 802 803 public View getAnimatedView() { 804 return mDropView; 805 } 806 807 @Override 808 public void onChildViewAdded(View parent, View child) { 809 super.onChildViewAdded(parent, child); 810 updateChildIndices(); 811 } 812 813 @Override 814 public void onChildViewRemoved(View parent, View child) { 815 updateChildIndices(); 816 } 817 818 @Override 819 public void bringChildToFront(View child) { 820 super.bringChildToFront(child); 821 updateChildIndices(); 822 } 823 824 private void updateChildIndices() { 825 mTopViewIndex = -1; 826 int childCount = getChildCount(); 827 for (int i = 0; i < childCount; i++) { 828 if (getChildAt(i) instanceof DragView) { 829 mTopViewIndex = i; 830 } 831 } 832 mChildCountOnLastUpdate = childCount; 833 } 834 835 @Override 836 protected int getChildDrawingOrder(int childCount, int i) { 837 if (mChildCountOnLastUpdate != childCount) { 838 // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed. 839 // Pre-18, the child was not added / removed by the time of those callbacks. We need to 840 // force update our representation of things here to avoid crashing on pre-18 devices 841 // in certain instances. 842 updateChildIndices(); 843 } 844 845 // i represents the current draw iteration 846 if (mTopViewIndex == -1) { 847 // in general we do nothing 848 return i; 849 } else if (i == childCount - 1) { 850 // if we have a top index, we return it when drawing last item (highest z-order) 851 return mTopViewIndex; 852 } else if (i < mTopViewIndex) { 853 return i; 854 } else { 855 // for indexes greater than the top index, we fetch one item above to shift for the 856 // displacement of the top index 857 return i + 1; 858 } 859 } 860 861 public void invalidateScrim() { 862 if (mBackgroundAlpha > 0.0f) { 863 invalidate(); 864 } 865 } 866 867 @Override 868 protected void dispatchDraw(Canvas canvas) { 869 // Draw the background below children. 870 if (mBackgroundAlpha > 0.0f) { 871 // Update the scroll position first to ensure scrim cutout is in the right place. 872 mLauncher.getWorkspace().computeScrollWithoutInvalidation(); 873 874 int alpha = (int) (mBackgroundAlpha * 255); 875 CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout(); 876 canvas.save(); 877 if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) { 878 // Cut a hole in the darkening scrim on the page that should be highlighted, if any. 879 getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect); 880 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); 881 } 882 // for super light wallpaper it needs to be darken for contrast to workspace 883 // for dark wallpapers the text is white so darkening works as well 884 int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor()); 885 canvas.drawColor(ColorUtils.setAlphaComponent(color, alpha)); 886 canvas.restore(); 887 } 888 889 mFocusIndicatorHelper.draw(canvas); 890 super.dispatchDraw(canvas); 891 } 892 893 public void setBackgroundAlpha(float alpha) { 894 if (alpha != mBackgroundAlpha) { 895 mBackgroundAlpha = alpha; 896 invalidate(); 897 } 898 } 899 900 public float getBackgroundAlpha() { 901 return mBackgroundAlpha; 902 } 903 904 @Override 905 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 906 View topView = AbstractFloatingView.getTopOpenView(mLauncher); 907 if (topView != null) { 908 return topView.requestFocus(direction, previouslyFocusedRect); 909 } else { 910 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 911 } 912 } 913 914 @Override 915 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 916 View topView = AbstractFloatingView.getTopOpenView(mLauncher); 917 if (topView != null) { 918 topView.addFocusables(views, direction); 919 } else { 920 super.addFocusables(views, direction, focusableMode); 921 } 922 } 923 924 public void setTouchCompleteListener(TouchCompleteListener listener) { 925 mTouchCompleteListener = listener; 926 } 927 928 public interface TouchCompleteListener { 929 public void onTouchComplete(); 930 } 931} 932