DragLayer.java revision 307fe23f125cbbd5512ad8d4660025f2ab68f30b
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.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.ViewParent; 35import android.view.accessibility.AccessibilityEvent; 36import android.view.accessibility.AccessibilityManager; 37import android.view.animation.DecelerateInterpolator; 38import android.view.animation.Interpolator; 39import android.widget.FrameLayout; 40import android.widget.TextView; 41 42import com.android.launcher.R; 43 44import java.util.ArrayList; 45 46/** 47 * A ViewGroup that coordinates dragging across its descendants 48 */ 49public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener { 50 private DragController mDragController; 51 private int[] mTmpXY = new int[2]; 52 53 private int mXDown, mYDown; 54 private Launcher mLauncher; 55 56 // Variables relating to resizing widgets 57 private final ArrayList<AppWidgetResizeFrame> mResizeFrames = 58 new ArrayList<AppWidgetResizeFrame>(); 59 private AppWidgetResizeFrame mCurrentResizeFrame; 60 61 // Variables relating to animation of views after drop 62 private ValueAnimator mDropAnim = null; 63 private ValueAnimator mFadeOutAnim = null; 64 private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 65 private DragView mDropView = null; 66 private int mAnchorViewInitialScrollX = 0; 67 private View mAnchorView = null; 68 69 private boolean mHoverPointClosesFolder = false; 70 private Rect mHitRect = new Rect(); 71 private int mWorkspaceIndex = -1; 72 private int mQsbIndex = -1; 73 public static final int ANIMATION_END_DISAPPEAR = 0; 74 public static final int ANIMATION_END_FADE_OUT = 1; 75 public static final int ANIMATION_END_REMAIN_VISIBLE = 2; 76 77 /** 78 * Used to create a new DragLayer from XML. 79 * 80 * @param context The application's context. 81 * @param attrs The attributes set containing the Workspace's customization values. 82 */ 83 public DragLayer(Context context, AttributeSet attrs) { 84 super(context, attrs); 85 86 // Disable multitouch across the workspace/all apps/customize tray 87 setMotionEventSplittingEnabled(false); 88 setChildrenDrawingOrderEnabled(true); 89 setOnHierarchyChangeListener(this); 90 91 mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo); 92 mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo); 93 } 94 95 public void setup(Launcher launcher, DragController controller) { 96 mLauncher = launcher; 97 mDragController = controller; 98 } 99 100 @Override 101 public boolean dispatchKeyEvent(KeyEvent event) { 102 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 103 } 104 105 private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { 106 getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); 107 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 108 return true; 109 } 110 return false; 111 } 112 113 private boolean isEventOverFolder(Folder folder, MotionEvent ev) { 114 getDescendantRectRelativeToSelf(folder, mHitRect); 115 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 116 return true; 117 } 118 return false; 119 } 120 121 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 122 Rect hitRect = new Rect(); 123 int x = (int) ev.getX(); 124 int y = (int) ev.getY(); 125 126 for (AppWidgetResizeFrame child: mResizeFrames) { 127 child.getHitRect(hitRect); 128 if (hitRect.contains(x, y)) { 129 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 130 mCurrentResizeFrame = child; 131 mXDown = x; 132 mYDown = y; 133 requestDisallowInterceptTouchEvent(true); 134 return true; 135 } 136 } 137 } 138 139 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 140 if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) { 141 if (currentFolder.isEditingName()) { 142 if (!isEventOverFolderTextRegion(currentFolder, ev)) { 143 currentFolder.dismissEditingName(); 144 return true; 145 } 146 } 147 148 getDescendantRectRelativeToSelf(currentFolder, hitRect); 149 if (!isEventOverFolder(currentFolder, ev)) { 150 mLauncher.closeFolder(); 151 return true; 152 } 153 } 154 return false; 155 } 156 157 @Override 158 public boolean onInterceptTouchEvent(MotionEvent ev) { 159 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 160 if (handleTouchDown(ev, true)) { 161 return true; 162 } 163 } 164 clearAllResizeFrames(); 165 return mDragController.onInterceptTouchEvent(ev); 166 } 167 168 @Override 169 public boolean onInterceptHoverEvent(MotionEvent ev) { 170 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 171 if (currentFolder == null) { 172 return false; 173 } else { 174 AccessibilityManager accessibilityManager = (AccessibilityManager) 175 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 176 if (accessibilityManager.isTouchExplorationEnabled()) { 177 final int action = ev.getAction(); 178 boolean isOverFolder; 179 switch (action) { 180 case MotionEvent.ACTION_HOVER_ENTER: 181 isOverFolder = isEventOverFolder(currentFolder, ev); 182 if (!isOverFolder) { 183 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 184 mHoverPointClosesFolder = true; 185 return true; 186 } else if (isOverFolder) { 187 mHoverPointClosesFolder = false; 188 } else { 189 return true; 190 } 191 case MotionEvent.ACTION_HOVER_MOVE: 192 isOverFolder = isEventOverFolder(currentFolder, ev); 193 if (!isOverFolder && !mHoverPointClosesFolder) { 194 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 195 mHoverPointClosesFolder = true; 196 return true; 197 } else if (isOverFolder) { 198 mHoverPointClosesFolder = false; 199 } else { 200 return true; 201 } 202 } 203 } 204 } 205 return false; 206 } 207 208 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { 209 AccessibilityManager accessibilityManager = (AccessibilityManager) 210 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 211 if (accessibilityManager.isEnabled()) { 212 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; 213 AccessibilityEvent event = AccessibilityEvent.obtain( 214 AccessibilityEvent.TYPE_VIEW_FOCUSED); 215 onInitializeAccessibilityEvent(event); 216 event.getText().add(getContext().getString(stringId)); 217 accessibilityManager.sendAccessibilityEvent(event); 218 } 219 } 220 221 @Override 222 public boolean onHoverEvent(MotionEvent ev) { 223 // If we've received this, we've already done the necessary handling 224 // in onInterceptHoverEvent. Return true to consume the event. 225 return false; 226 } 227 228 @Override 229 public boolean onTouchEvent(MotionEvent ev) { 230 boolean handled = false; 231 int action = ev.getAction(); 232 233 int x = (int) ev.getX(); 234 int y = (int) ev.getY(); 235 236 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 237 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 238 if (handleTouchDown(ev, false)) { 239 return true; 240 } 241 } 242 } 243 244 if (mCurrentResizeFrame != null) { 245 handled = true; 246 switch (action) { 247 case MotionEvent.ACTION_MOVE: 248 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 249 break; 250 case MotionEvent.ACTION_CANCEL: 251 case MotionEvent.ACTION_UP: 252 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 253 mCurrentResizeFrame.onTouchUp(); 254 mCurrentResizeFrame = null; 255 } 256 } 257 if (handled) return true; 258 return mDragController.onTouchEvent(ev); 259 } 260 261 /** 262 * Determine the rect of the descendant in this DragLayer's coordinates 263 * 264 * @param descendant The descendant whose coordinates we want to find. 265 * @param r The rect into which to place the results. 266 * @return The factor by which this descendant is scaled relative to this DragLayer. 267 */ 268 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 269 mTmpXY[0] = 0; 270 mTmpXY[1] = 0; 271 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 272 r.set(mTmpXY[0], mTmpXY[1], 273 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); 274 return scale; 275 } 276 277 public float getLocationInDragLayer(View child, int[] loc) { 278 loc[0] = 0; 279 loc[1] = 0; 280 return getDescendantCoordRelativeToSelf(child, loc); 281 } 282 283 /** 284 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 285 * coordinates. 286 * 287 * @param descendant The descendant to which the passed coordinate is relative. 288 * @param coord The coordinate that we want mapped. 289 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 290 * this scale factor is assumed to be equal in X and Y, and so if at any point this 291 * assumption fails, we will need to return a pair of scale factors. 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.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 450 parentChildren.measureChild(child); 451 452 Rect r = new Rect(); 453 getViewRectRelativeToSelf(dragView, r); 454 455 int coord[] = new int[2]; 456 float childScale = child.getScaleX(); 457 coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2); 458 coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2); 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 // We need to account for the scale of the child itself, as the above only accounts for 464 // for the scale in parents. 465 scale *= childScale; 466 int toX = coord[0]; 467 int toY = coord[1]; 468 if (child instanceof TextView) { 469 TextView tv = (TextView) child; 470 471 // The child may be scaled (always about the center of the view) so to account for it, 472 // we have to offset the position by the scaled size. Once we do that, we can center 473 // the drag view about the scaled child view. 474 toY += Math.round(scale * tv.getPaddingTop()); 475 toY -= dragView.getMeasuredHeight() * (1 - scale) / 2; 476 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 477 } else if (child instanceof FolderIcon) { 478 // Account for holographic blur padding on the drag view 479 toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2; 480 toY -= (1 - scale) * dragView.getMeasuredHeight() / 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