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