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.ObjectAnimator; 22import android.animation.TimeInterpolator; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.content.Context; 26import android.content.res.Resources; 27import android.graphics.Canvas; 28import android.graphics.Rect; 29import android.graphics.drawable.Drawable; 30import android.util.AttributeSet; 31import android.view.KeyEvent; 32import android.view.MotionEvent; 33import android.view.View; 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 { 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 View mDropView = null; 66 67 private int[] mDropViewPos = new int[2]; 68 private float mDropViewScale; 69 private float mDropViewAlpha; 70 private boolean mHoverPointClosesFolder = false; 71 private Rect mHitRect = new Rect(); 72 private int mWorkspaceIndex = -1; 73 private int mQsbIndex = -1; 74 75 /** 76 * Used to create a new DragLayer from XML. 77 * 78 * @param context The application's context. 79 * @param attrs The attributes set containing the Workspace's customization values. 80 */ 81 public DragLayer(Context context, AttributeSet attrs) { 82 super(context, attrs); 83 84 // Disable multitouch across the workspace/all apps/customize tray 85 setMotionEventSplittingEnabled(false); 86 setChildrenDrawingOrderEnabled(true); 87 } 88 89 public void setup(Launcher launcher, DragController controller) { 90 mLauncher = launcher; 91 mDragController = controller; 92 } 93 94 @Override 95 public boolean dispatchKeyEvent(KeyEvent event) { 96 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 97 } 98 99 private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { 100 getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); 101 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 102 return true; 103 } 104 return false; 105 } 106 107 private boolean isEventOverFolder(Folder folder, MotionEvent ev) { 108 getDescendantRectRelativeToSelf(folder, mHitRect); 109 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 110 return true; 111 } 112 return false; 113 } 114 115 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 116 Rect hitRect = new Rect(); 117 int x = (int) ev.getX(); 118 int y = (int) ev.getY(); 119 120 for (AppWidgetResizeFrame child: mResizeFrames) { 121 child.getHitRect(hitRect); 122 if (hitRect.contains(x, y)) { 123 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 124 mCurrentResizeFrame = child; 125 mXDown = x; 126 mYDown = y; 127 requestDisallowInterceptTouchEvent(true); 128 return true; 129 } 130 } 131 } 132 133 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 134 if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) { 135 if (currentFolder.isEditingName()) { 136 if (!isEventOverFolderTextRegion(currentFolder, ev)) { 137 currentFolder.dismissEditingName(); 138 return true; 139 } 140 } 141 142 getDescendantRectRelativeToSelf(currentFolder, hitRect); 143 if (!isEventOverFolder(currentFolder, ev)) { 144 mLauncher.closeFolder(); 145 return true; 146 } 147 } 148 return false; 149 } 150 151 @Override 152 public boolean onInterceptTouchEvent(MotionEvent ev) { 153 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 154 if (handleTouchDown(ev, true)) { 155 return true; 156 } 157 } 158 clearAllResizeFrames(); 159 return mDragController.onInterceptTouchEvent(ev); 160 } 161 162 @Override 163 public boolean onInterceptHoverEvent(MotionEvent ev) { 164 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 165 if (currentFolder == null) { 166 return false; 167 } else { 168 if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { 169 final int action = ev.getAction(); 170 boolean isOverFolder; 171 switch (action) { 172 case MotionEvent.ACTION_HOVER_ENTER: 173 isOverFolder = isEventOverFolder(currentFolder, ev); 174 if (!isOverFolder) { 175 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 176 mHoverPointClosesFolder = true; 177 return true; 178 } else if (isOverFolder) { 179 mHoverPointClosesFolder = false; 180 } else { 181 return true; 182 } 183 case MotionEvent.ACTION_HOVER_MOVE: 184 isOverFolder = isEventOverFolder(currentFolder, ev); 185 if (!isOverFolder && !mHoverPointClosesFolder) { 186 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 187 mHoverPointClosesFolder = true; 188 return true; 189 } else if (isOverFolder) { 190 mHoverPointClosesFolder = false; 191 } else { 192 return true; 193 } 194 } 195 } 196 } 197 return false; 198 } 199 200 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { 201 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 202 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; 203 AccessibilityEvent event = AccessibilityEvent.obtain( 204 AccessibilityEvent.TYPE_VIEW_FOCUSED); 205 onInitializeAccessibilityEvent(event); 206 event.getText().add(mContext.getString(stringId)); 207 AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event); 208 } 209 } 210 211 @Override 212 public boolean onHoverEvent(MotionEvent ev) { 213 // If we've received this, we've already done the necessary handling 214 // in onInterceptHoverEvent. Return true to consume the event. 215 return false; 216 } 217 218 @Override 219 public boolean onTouchEvent(MotionEvent ev) { 220 boolean handled = false; 221 int action = ev.getAction(); 222 223 int x = (int) ev.getX(); 224 int y = (int) ev.getY(); 225 226 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 227 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 228 if (handleTouchDown(ev, false)) { 229 return true; 230 } 231 } 232 } 233 234 if (mCurrentResizeFrame != null) { 235 handled = true; 236 switch (action) { 237 case MotionEvent.ACTION_MOVE: 238 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 239 break; 240 case MotionEvent.ACTION_CANCEL: 241 case MotionEvent.ACTION_UP: 242 mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown); 243 mCurrentResizeFrame = null; 244 } 245 } 246 if (handled) return true; 247 return mDragController.onTouchEvent(ev); 248 } 249 250 /** 251 * Determine the rect of the descendant in this DragLayer's coordinates 252 * 253 * @param descendant The descendant whose coordinates we want to find. 254 * @param r The rect into which to place the results. 255 * @return The factor by which this descendant is scaled relative to this DragLayer. 256 */ 257 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 258 mTmpXY[0] = 0; 259 mTmpXY[1] = 0; 260 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 261 r.set(mTmpXY[0], mTmpXY[1], 262 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); 263 return scale; 264 } 265 266 public void getLocationInDragLayer(View child, int[] loc) { 267 loc[0] = 0; 268 loc[1] = 0; 269 getDescendantCoordRelativeToSelf(child, loc); 270 } 271 272 /** 273 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 274 * coordinates. 275 * 276 * @param descendant The descendant to which the passed coordinate is relative. 277 * @param coord The coordinate that we want mapped. 278 * @return The factor by which this descendant is scaled relative to this DragLayer. 279 */ 280 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 281 float scale = 1.0f; 282 float[] pt = {coord[0], coord[1]}; 283 descendant.getMatrix().mapPoints(pt); 284 scale *= descendant.getScaleX(); 285 pt[0] += descendant.getLeft(); 286 pt[1] += descendant.getTop(); 287 ViewParent viewParent = descendant.getParent(); 288 while (viewParent instanceof View && viewParent != this) { 289 final View view = (View)viewParent; 290 view.getMatrix().mapPoints(pt); 291 scale *= view.getScaleX(); 292 pt[0] += view.getLeft() - view.getScrollX(); 293 pt[1] += view.getTop() - view.getScrollY(); 294 viewParent = view.getParent(); 295 } 296 coord[0] = (int) Math.round(pt[0]); 297 coord[1] = (int) Math.round(pt[1]); 298 return scale; 299 } 300 301 public void getViewRectRelativeToSelf(View v, Rect r) { 302 int[] loc = new int[2]; 303 getLocationInWindow(loc); 304 int x = loc[0]; 305 int y = loc[1]; 306 307 v.getLocationInWindow(loc); 308 int vX = loc[0]; 309 int vY = loc[1]; 310 311 int left = vX - x; 312 int top = vY - y; 313 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 314 } 315 316 @Override 317 public boolean dispatchUnhandledMove(View focused, int direction) { 318 return mDragController.dispatchUnhandledMove(focused, direction); 319 } 320 321 public static class LayoutParams extends FrameLayout.LayoutParams { 322 public int x, y; 323 public boolean customPosition = false; 324 325 /** 326 * {@inheritDoc} 327 */ 328 public LayoutParams(int width, int height) { 329 super(width, height); 330 } 331 332 public void setWidth(int width) { 333 this.width = width; 334 } 335 336 public int getWidth() { 337 return width; 338 } 339 340 public void setHeight(int height) { 341 this.height = height; 342 } 343 344 public int getHeight() { 345 return height; 346 } 347 348 public void setX(int x) { 349 this.x = x; 350 } 351 352 public int getX() { 353 return x; 354 } 355 356 public void setY(int y) { 357 this.y = y; 358 } 359 360 public int getY() { 361 return y; 362 } 363 } 364 365 protected void onLayout(boolean changed, int l, int t, int r, int b) { 366 super.onLayout(changed, l, t, r, b); 367 int count = getChildCount(); 368 for (int i = 0; i < count; i++) { 369 View child = getChildAt(i); 370 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 371 if (flp instanceof LayoutParams) { 372 final LayoutParams lp = (LayoutParams) flp; 373 if (lp.customPosition) { 374 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 375 } 376 } 377 } 378 } 379 380 public void clearAllResizeFrames() { 381 if (mResizeFrames.size() > 0) { 382 for (AppWidgetResizeFrame frame: mResizeFrames) { 383 removeView(frame); 384 } 385 mResizeFrames.clear(); 386 } 387 } 388 389 public boolean hasResizeFrames() { 390 return mResizeFrames.size() > 0; 391 } 392 393 public boolean isWidgetBeingResized() { 394 return mCurrentResizeFrame != null; 395 } 396 397 public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, 398 CellLayout cellLayout) { 399 AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), 400 itemInfo, widget, cellLayout, this); 401 402 LayoutParams lp = new LayoutParams(-1, -1); 403 lp.customPosition = true; 404 405 addView(resizeFrame, lp); 406 mResizeFrames.add(resizeFrame); 407 408 resizeFrame.snapToWidget(false); 409 } 410 411 public void animateViewIntoPosition(DragView dragView, final View child) { 412 animateViewIntoPosition(dragView, child, null); 413 } 414 415 public void animateViewIntoPosition(DragView dragView, final int[] pos, float scale, 416 Runnable onFinishRunnable) { 417 Rect r = new Rect(); 418 getViewRectRelativeToSelf(dragView, r); 419 final int fromX = r.left; 420 final int fromY = r.top; 421 422 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], scale, 423 onFinishRunnable, true, -1); 424 } 425 426 public void animateViewIntoPosition(DragView dragView, final View child, 427 final Runnable onFinishAnimationRunnable) { 428 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable); 429 } 430 431 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 432 final Runnable onFinishAnimationRunnable) { 433 ((CellLayoutChildren) child.getParent()).measureChild(child); 434 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 435 436 Rect r = new Rect(); 437 getViewRectRelativeToSelf(dragView, r); 438 439 int coord[] = new int[2]; 440 coord[0] = lp.x; 441 coord[1] = lp.y; 442 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 443 // the correct coordinates (above) and use these to determine the final location 444 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 445 int toX = coord[0]; 446 int toY = coord[1]; 447 if (child instanceof TextView) { 448 TextView tv = (TextView) child; 449 Drawable d = tv.getCompoundDrawables()[1]; 450 451 // Center in the y coordinate about the target's drawable 452 toY += Math.round(scale * tv.getPaddingTop()); 453 toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2; 454 // Center in the x coordinate about the target's drawable 455 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 456 } else if (child instanceof FolderIcon) { 457 // Account for holographic blur padding on the drag view 458 toY -= HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2; 459 // Center in the x coordinate about the target's drawable 460 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 461 } else { 462 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 463 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 464 - child.getMeasuredWidth()))) / 2; 465 } 466 467 final int fromX = r.left; 468 final int fromY = r.top; 469 child.setVisibility(INVISIBLE); 470 child.setAlpha(0); 471 Runnable onCompleteRunnable = new Runnable() { 472 public void run() { 473 child.setVisibility(VISIBLE); 474 ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f); 475 oa.setDuration(60); 476 oa.addListener(new AnimatorListenerAdapter() { 477 @Override 478 public void onAnimationEnd(android.animation.Animator animation) { 479 if (onFinishAnimationRunnable != null) { 480 onFinishAnimationRunnable.run(); 481 } 482 } 483 }); 484 oa.start(); 485 } 486 }; 487 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, scale, 488 onCompleteRunnable, true, duration); 489 } 490 491 private void animateViewIntoPosition(final View view, final int fromX, final int fromY, 492 final int toX, final int toY, float finalScale, Runnable onCompleteRunnable, 493 boolean fadeOut, int duration) { 494 Rect from = new Rect(fromX, fromY, fromX + 495 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 496 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 497 animateView(view, from, to, 1f, finalScale, duration, null, null, onCompleteRunnable, true); 498 } 499 500 /** 501 * This method animates a view at the end of a drag and drop animation. 502 * 503 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 504 * doesn't need to be a child of DragLayer. 505 * @param from The initial location of the view. Only the left and top parameters are used. 506 * @param to The final location of the view. Only the left and top parameters are used. This 507 * location doesn't account for scaling, and so should be centered about the desired 508 * final location (including scaling). 509 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 510 * @param finalScale The final scale of the view. The view is scaled about its center. 511 * @param duration The duration of the animation. 512 * @param motionInterpolator The interpolator to use for the location of the view. 513 * @param alphaInterpolator The interpolator to use for the alpha of the view. 514 * @param onCompleteRunnable Optional runnable to run on animation completion. 515 * @param fadeOut Whether or not to fade out the view once the animation completes. If true, 516 * the runnable will execute after the view is faded out. 517 */ 518 public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha, 519 final float finalScale, int duration, final Interpolator motionInterpolator, 520 final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, 521 final boolean fadeOut) { 522 // Calculate the duration of the animation based on the object's distance 523 final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + 524 Math.pow(to.top - from.top, 2)); 525 final Resources res = getResources(); 526 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 527 528 // If duration < 0, this is a cue to compute the duration based on the distance 529 if (duration < 0) { 530 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 531 if (dist < maxDist) { 532 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 533 } 534 } 535 536 if (mDropAnim != null) { 537 mDropAnim.cancel(); 538 } 539 540 if (mFadeOutAnim != null) { 541 mFadeOutAnim.cancel(); 542 } 543 544 mDropView = view; 545 final float initialAlpha = view.getAlpha(); 546 mDropAnim = new ValueAnimator(); 547 if (alphaInterpolator == null || motionInterpolator == null) { 548 mDropAnim.setInterpolator(mCubicEaseOutInterpolator); 549 } 550 551 mDropAnim.setDuration(duration); 552 mDropAnim.setFloatValues(0.0f, 1.0f); 553 mDropAnim.removeAllUpdateListeners(); 554 mDropAnim.addUpdateListener(new AnimatorUpdateListener() { 555 public void onAnimationUpdate(ValueAnimator animation) { 556 final float percent = (Float) animation.getAnimatedValue(); 557 // Invalidate the old position 558 int width = view.getMeasuredWidth(); 559 int height = view.getMeasuredHeight(); 560 invalidate(mDropViewPos[0], mDropViewPos[1], 561 mDropViewPos[0] + width, mDropViewPos[1] + height); 562 563 float alphaPercent = alphaInterpolator == null ? percent : 564 alphaInterpolator.getInterpolation(percent); 565 float motionPercent = motionInterpolator == null ? percent : 566 motionInterpolator.getInterpolation(percent); 567 568 mDropViewPos[0] = from.left + (int) Math.round(((to.left - from.left) * motionPercent)); 569 mDropViewPos[1] = from.top + (int) Math.round(((to.top - from.top) * motionPercent)); 570 mDropViewScale = percent * finalScale + (1 - percent); 571 mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha; 572 invalidate(mDropViewPos[0], mDropViewPos[1], 573 mDropViewPos[0] + width, mDropViewPos[1] + height); 574 } 575 }); 576 mDropAnim.addListener(new AnimatorListenerAdapter() { 577 public void onAnimationEnd(Animator animation) { 578 if (onCompleteRunnable != null) { 579 onCompleteRunnable.run(); 580 } 581 if (fadeOut) { 582 fadeOutDragView(); 583 } else { 584 mDropView = null; 585 } 586 } 587 }); 588 mDropAnim.start(); 589 } 590 591 private void fadeOutDragView() { 592 mFadeOutAnim = new ValueAnimator(); 593 mFadeOutAnim.setDuration(150); 594 mFadeOutAnim.setFloatValues(0f, 1f); 595 mFadeOutAnim.removeAllUpdateListeners(); 596 mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() { 597 public void onAnimationUpdate(ValueAnimator animation) { 598 final float percent = (Float) animation.getAnimatedValue(); 599 mDropViewAlpha = 1 - percent; 600 int width = mDropView.getMeasuredWidth(); 601 int height = mDropView.getMeasuredHeight(); 602 invalidate(mDropViewPos[0], mDropViewPos[1], 603 mDropViewPos[0] + width, mDropViewPos[1] + height); 604 } 605 }); 606 mFadeOutAnim.addListener(new AnimatorListenerAdapter() { 607 public void onAnimationEnd(Animator animation) { 608 mDropView = null; 609 } 610 }); 611 mFadeOutAnim.start(); 612 } 613 614 @Override 615 protected void onViewAdded(View child) { 616 super.onViewAdded(child); 617 updateChildIndices(); 618 } 619 620 @Override 621 protected void onViewRemoved(View child) { 622 super.onViewRemoved(child); 623 updateChildIndices(); 624 } 625 626 private void updateChildIndices() { 627 if (mLauncher != null) { 628 mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace()); 629 mQsbIndex = indexOfChild(mLauncher.getSearchBar()); 630 } 631 } 632 633 @Override 634 protected int getChildDrawingOrder(int childCount, int i) { 635 // We don't want to prioritize the workspace drawing on top of the other children in 636 // landscape for the overscroll event. 637 if (LauncherApplication.isScreenLandscape(getContext())) { 638 return super.getChildDrawingOrder(childCount, i); 639 } 640 641 if (mWorkspaceIndex == -1 || mQsbIndex == -1 || 642 mLauncher.getWorkspace().isDrawingBackgroundGradient()) { 643 return i; 644 } 645 646 // This ensures that the workspace is drawn above the hotseat and qsb, 647 // except when the workspace is drawing a background gradient, in which 648 // case we want the workspace to stay behind these elements. 649 if (i == mQsbIndex) { 650 return mWorkspaceIndex; 651 } else if (i == mWorkspaceIndex) { 652 return mQsbIndex; 653 } else { 654 return i; 655 } 656 } 657 658 @Override 659 protected void dispatchDraw(Canvas canvas) { 660 super.dispatchDraw(canvas); 661 if (mDropView != null) { 662 // We are animating an item that was just dropped on the home screen. 663 // Render its View in the current animation position. 664 canvas.save(Canvas.MATRIX_SAVE_FLAG); 665 final int xPos = mDropViewPos[0] - mDropView.getScrollX(); 666 final int yPos = mDropViewPos[1] - mDropView.getScrollY(); 667 int width = mDropView.getMeasuredWidth(); 668 int height = mDropView.getMeasuredHeight(); 669 canvas.translate(xPos, yPos); 670 canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2); 671 canvas.scale(mDropViewScale, mDropViewScale); 672 mDropView.setAlpha(mDropViewAlpha); 673 mDropView.draw(canvas); 674 canvas.restore(); 675 } 676 } 677} 678