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