DragLayer.java revision 360e63fd3e77247002b86da2a77bd8dfe8c8a807
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.launcher2; 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.PorterDuff; 28import android.graphics.PorterDuffColorFilter; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.util.AttributeSet; 32import android.view.KeyEvent; 33import android.view.MotionEvent; 34import android.view.View; 35import android.view.ViewGroup; 36import android.view.ViewParent; 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.launcher.R; 45 46import java.util.ArrayList; 47 48/** 49 * A ViewGroup that coordinates dragging across its descendants 50 */ 51public 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 float childrenScale = parent.getChildrenScale(); 467 TextView tv = (TextView) child; 468 469 // The child may be scaled (always about the center of the view) so to account for it, 470 // we have to offset the position by the scaled size. Once we do that, we can center 471 // the drag view about the scaled child view. 472 toY += Math.round(((1f - childrenScale) * child.getMeasuredHeight()) / 2 + 473 scale * childrenScale * tv.getPaddingTop()); 474 toY -= dragView.getMeasuredHeight() * (1 - scale * childrenScale) / 2; 475 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 476 477 scale *= childrenScale; 478 } else if (child instanceof FolderIcon) { 479 // Account for holographic blur padding on the drag view 480 toY -= Workspace.DRAG_BITMAP_PADDING / 2; 481 // Center in the x coordinate about the target's drawable 482 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 483 } else { 484 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 485 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 486 - child.getMeasuredWidth()))) / 2; 487 } 488 489 final int fromX = r.left; 490 final int fromY = r.top; 491 child.setVisibility(INVISIBLE); 492 Runnable onCompleteRunnable = new Runnable() { 493 public void run() { 494 child.setVisibility(VISIBLE); 495 if (onFinishAnimationRunnable != null) { 496 onFinishAnimationRunnable.run(); 497 } 498 } 499 }; 500 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale, 501 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); 502 } 503 504 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, 505 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, 506 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, 507 int animationEndStyle, int duration, View anchorView) { 508 Rect from = new Rect(fromX, fromY, fromX + 509 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 510 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 511 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, 512 null, null, onCompleteRunnable, animationEndStyle, anchorView); 513 } 514 515 /** 516 * This method animates a view at the end of a drag and drop animation. 517 * 518 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 519 * doesn't need to be a child of DragLayer. 520 * @param from The initial location of the view. Only the left and top parameters are used. 521 * @param to The final location of the view. Only the left and top parameters are used. This 522 * location doesn't account for scaling, and so should be centered about the desired 523 * final location (including scaling). 524 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 525 * @param finalScale The final scale of the view. The view is scaled about its center. 526 * @param duration The duration of the animation. 527 * @param motionInterpolator The interpolator to use for the location of the view. 528 * @param alphaInterpolator The interpolator to use for the alpha of the view. 529 * @param onCompleteRunnable Optional runnable to run on animation completion. 530 * @param fadeOut Whether or not to fade out the view once the animation completes. If true, 531 * the runnable will execute after the view is faded out. 532 * @param anchorView If not null, this represents the view which the animated view stays 533 * anchored to in case scrolling is currently taking place. Note: currently this is 534 * only used for the X dimension for the case of the workspace. 535 */ 536 public void animateView(final DragView view, final Rect from, final Rect to, 537 final float finalAlpha, final float initScaleX, final float initScaleY, 538 final float finalScaleX, final float finalScaleY, int duration, 539 final Interpolator motionInterpolator, final Interpolator alphaInterpolator, 540 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { 541 542 // Calculate the duration of the animation based on the object's distance 543 final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + 544 Math.pow(to.top - from.top, 2)); 545 final Resources res = getResources(); 546 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 547 548 // If duration < 0, this is a cue to compute the duration based on the distance 549 if (duration < 0) { 550 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 551 if (dist < maxDist) { 552 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 553 } 554 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); 555 } 556 557 // Fall back to cubic ease out interpolator for the animation if none is specified 558 TimeInterpolator interpolator = null; 559 if (alphaInterpolator == null || motionInterpolator == null) { 560 interpolator = mCubicEaseOutInterpolator; 561 } 562 563 // Animate the view 564 final float initAlpha = view.getAlpha(); 565 final float dropViewScale = view.getScaleX(); 566 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { 567 @Override 568 public void onAnimationUpdate(ValueAnimator animation) { 569 final float percent = (Float) animation.getAnimatedValue(); 570 final int width = view.getMeasuredWidth(); 571 final int height = view.getMeasuredHeight(); 572 573 float alphaPercent = alphaInterpolator == null ? percent : 574 alphaInterpolator.getInterpolation(percent); 575 float motionPercent = motionInterpolator == null ? percent : 576 motionInterpolator.getInterpolation(percent); 577 578 float initialScaleX = initScaleX * dropViewScale; 579 float initialScaleY = initScaleY * dropViewScale; 580 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); 581 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); 582 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); 583 584 float fromLeft = from.left + (initialScaleX - 1f) * width / 2; 585 float fromTop = from.top + (initialScaleY - 1f) * height / 2; 586 587 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); 588 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); 589 590 int xPos = x - mDropView.getScrollX() + (mAnchorView != null 591 ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0); 592 int yPos = y - mDropView.getScrollY(); 593 594 mDropView.setTranslationX(xPos); 595 mDropView.setTranslationY(yPos); 596 mDropView.setScaleX(scaleX); 597 mDropView.setScaleY(scaleY); 598 mDropView.setAlpha(alpha); 599 } 600 }; 601 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, 602 anchorView); 603 } 604 605 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, 606 TimeInterpolator interpolator, final Runnable onCompleteRunnable, 607 final int animationEndStyle, View anchorView) { 608 // Clean up the previous animations 609 if (mDropAnim != null) mDropAnim.cancel(); 610 if (mFadeOutAnim != null) mFadeOutAnim.cancel(); 611 612 // Show the drop view if it was previously hidden 613 mDropView = view; 614 mDropView.cancelAnimation(); 615 mDropView.resetLayoutParams(); 616 617 // Set the anchor view if the page is scrolling 618 if (anchorView != null) { 619 mAnchorViewInitialScrollX = anchorView.getScrollX(); 620 } 621 mAnchorView = anchorView; 622 623 // Create and start the animation 624 mDropAnim = new ValueAnimator(); 625 mDropAnim.setInterpolator(interpolator); 626 mDropAnim.setDuration(duration); 627 mDropAnim.setFloatValues(0f, 1f); 628 mDropAnim.addUpdateListener(updateCb); 629 mDropAnim.addListener(new AnimatorListenerAdapter() { 630 public void onAnimationEnd(Animator animation) { 631 if (onCompleteRunnable != null) { 632 onCompleteRunnable.run(); 633 } 634 switch (animationEndStyle) { 635 case ANIMATION_END_DISAPPEAR: 636 clearAnimatedView(); 637 break; 638 case ANIMATION_END_FADE_OUT: 639 fadeOutDragView(); 640 break; 641 case ANIMATION_END_REMAIN_VISIBLE: 642 break; 643 } 644 } 645 }); 646 mDropAnim.start(); 647 } 648 649 public void clearAnimatedView() { 650 if (mDropAnim != null) { 651 mDropAnim.cancel(); 652 } 653 if (mDropView != null) { 654 mDragController.onDeferredEndDrag(mDropView); 655 } 656 mDropView = null; 657 invalidate(); 658 } 659 660 public View getAnimatedView() { 661 return mDropView; 662 } 663 664 private void fadeOutDragView() { 665 mFadeOutAnim = new ValueAnimator(); 666 mFadeOutAnim.setDuration(150); 667 mFadeOutAnim.setFloatValues(0f, 1f); 668 mFadeOutAnim.removeAllUpdateListeners(); 669 mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() { 670 public void onAnimationUpdate(ValueAnimator animation) { 671 final float percent = (Float) animation.getAnimatedValue(); 672 673 float alpha = 1 - percent; 674 mDropView.setAlpha(alpha); 675 } 676 }); 677 mFadeOutAnim.addListener(new AnimatorListenerAdapter() { 678 public void onAnimationEnd(Animator animation) { 679 if (mDropView != null) { 680 mDragController.onDeferredEndDrag(mDropView); 681 } 682 mDropView = null; 683 invalidate(); 684 } 685 }); 686 mFadeOutAnim.start(); 687 } 688 689 @Override 690 public void onChildViewAdded(View parent, View child) { 691 updateChildIndices(); 692 } 693 694 @Override 695 public void onChildViewRemoved(View parent, View child) { 696 updateChildIndices(); 697 } 698 699 private void updateChildIndices() { 700 if (mLauncher != null) { 701 mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace()); 702 mQsbIndex = indexOfChild(mLauncher.getSearchBar()); 703 } 704 } 705 706 @Override 707 protected int getChildDrawingOrder(int childCount, int i) { 708 // We don't want to prioritize the workspace drawing on top of the other children in 709 // landscape for the overscroll event. 710 if (LauncherApplication.isScreenLandscape(getContext())) { 711 return super.getChildDrawingOrder(childCount, i); 712 } 713 714 if (mWorkspaceIndex == -1 || mQsbIndex == -1 || 715 mLauncher.getWorkspace().isDrawingBackgroundGradient()) { 716 return i; 717 } 718 719 // This ensures that the workspace is drawn above the hotseat and qsb, 720 // except when the workspace is drawing a background gradient, in which 721 // case we want the workspace to stay behind these elements. 722 if (i == mQsbIndex) { 723 return mWorkspaceIndex; 724 } else if (i == mWorkspaceIndex) { 725 return mQsbIndex; 726 } else { 727 return i; 728 } 729 } 730 731 private boolean mInScrollArea; 732 private Drawable mLeftHoverDrawable; 733 private Drawable mRightHoverDrawable; 734 735 void onEnterScrollArea(int direction) { 736 mInScrollArea = true; 737 invalidate(); 738 } 739 740 void onExitScrollArea() { 741 mInScrollArea = false; 742 invalidate(); 743 } 744 745 @Override 746 protected void dispatchDraw(Canvas canvas) { 747 super.dispatchDraw(canvas); 748 749 if (mInScrollArea && !LauncherApplication.isScreenLarge()) { 750 Workspace workspace = mLauncher.getWorkspace(); 751 int width = workspace.getWidth(); 752 Rect childRect = new Rect(); 753 getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect); 754 755 int page = workspace.getNextPage(); 756 CellLayout leftPage = (CellLayout) workspace.getChildAt(page - 1); 757 CellLayout rightPage = (CellLayout) workspace.getChildAt(page + 1); 758 759 if (leftPage != null && leftPage.getIsDragOverlapping()) { 760 mLeftHoverDrawable.setBounds(0, childRect.top, 761 mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom); 762 mLeftHoverDrawable.draw(canvas); 763 } else if (rightPage != null && rightPage.getIsDragOverlapping()) { 764 mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(), 765 childRect.top, width, childRect.bottom); 766 mRightHoverDrawable.draw(canvas); 767 } 768 } 769 } 770} 771