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