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