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