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.launcher3; 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.Color; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.util.AttributeSet; 31 import android.view.KeyEvent; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.view.ViewGroup; 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.launcher3.accessibility.LauncherAccessibilityDelegate; 43 import com.android.launcher3.util.Thunk; 44 45 import java.util.ArrayList; 46 47 /** 48 * A ViewGroup that coordinates dragging across its descendants 49 */ 50 public class DragLayer extends InsettableFrameLayout { 51 52 public static final int ANIMATION_END_DISAPPEAR = 0; 53 public static final int ANIMATION_END_REMAIN_VISIBLE = 2; 54 55 // Scrim color without any alpha component. 56 private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF; 57 58 private final int[] mTmpXY = new int[2]; 59 60 @Thunk DragController mDragController; 61 62 private int mXDown, mYDown; 63 private Launcher mLauncher; 64 65 // Variables relating to resizing widgets 66 private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>(); 67 private final boolean mIsRtl; 68 private AppWidgetResizeFrame mCurrentResizeFrame; 69 70 // Variables relating to animation of views after drop 71 private ValueAnimator mDropAnim = null; 72 private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 73 @Thunk DragView mDropView = null; 74 @Thunk int mAnchorViewInitialScrollX = 0; 75 @Thunk View mAnchorView = null; 76 77 private boolean mHoverPointClosesFolder = false; 78 private final Rect mHitRect = new Rect(); 79 80 private TouchCompleteListener mTouchCompleteListener; 81 82 private View mOverlayView; 83 private int mTopViewIndex; 84 private int mChildCountOnLastUpdate = -1; 85 86 // Darkening scrim 87 private float mBackgroundAlpha = 0; 88 89 // Related to adjacent page hints 90 private final Rect mScrollChildPosition = new Rect(); 91 private boolean mInScrollArea; 92 private boolean mShowPageHints; 93 private Drawable mLeftHoverDrawable; 94 private Drawable mRightHoverDrawable; 95 private Drawable mLeftHoverDrawableActive; 96 private Drawable mRightHoverDrawableActive; 97 98 /** 99 * Used to create a new DragLayer from XML. 100 * 101 * @param context The application's context. 102 * @param attrs The attributes set containing the Workspace's customization values. 103 */ DragLayer(Context context, AttributeSet attrs)104 public DragLayer(Context context, AttributeSet attrs) { 105 super(context, attrs); 106 107 // Disable multitouch across the workspace/all apps/customize tray 108 setMotionEventSplittingEnabled(false); 109 setChildrenDrawingOrderEnabled(true); 110 111 final Resources res = getResources(); 112 mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left); 113 mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right); 114 mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active); 115 mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active); 116 mIsRtl = Utilities.isRtl(res); 117 } 118 setup(Launcher launcher, DragController controller)119 public void setup(Launcher launcher, DragController controller) { 120 mLauncher = launcher; 121 mDragController = controller; 122 } 123 124 @Override dispatchKeyEvent(KeyEvent event)125 public boolean dispatchKeyEvent(KeyEvent event) { 126 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 127 } 128 showOverlayView(View overlayView)129 public void showOverlayView(View overlayView) { 130 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 131 mOverlayView = overlayView; 132 addView(overlayView, lp); 133 134 // ensure that the overlay view stays on top. we can't use drawing order for this 135 // because in API level 16 touch dispatch doesn't respect drawing order. 136 mOverlayView.bringToFront(); 137 } 138 dismissOverlayView()139 public void dismissOverlayView() { 140 removeView(mOverlayView); 141 } 142 isEventOverFolderTextRegion(Folder folder, MotionEvent ev)143 private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { 144 getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); 145 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 146 return true; 147 } 148 return false; 149 } 150 isEventOverFolder(Folder folder, MotionEvent ev)151 private boolean isEventOverFolder(Folder folder, MotionEvent ev) { 152 getDescendantRectRelativeToSelf(folder, mHitRect); 153 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 154 return true; 155 } 156 return false; 157 } 158 isEventOverDropTargetBar(MotionEvent ev)159 private boolean isEventOverDropTargetBar(MotionEvent ev) { 160 getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect); 161 if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { 162 return true; 163 } 164 return false; 165 } 166 handleTouchDown(MotionEvent ev, boolean intercept)167 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 168 Rect hitRect = new Rect(); 169 int x = (int) ev.getX(); 170 int y = (int) ev.getY(); 171 172 for (AppWidgetResizeFrame child: mResizeFrames) { 173 child.getHitRect(hitRect); 174 if (hitRect.contains(x, y)) { 175 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 176 mCurrentResizeFrame = child; 177 mXDown = x; 178 mYDown = y; 179 requestDisallowInterceptTouchEvent(true); 180 return true; 181 } 182 } 183 } 184 185 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 186 if (currentFolder != null && intercept) { 187 if (currentFolder.isEditingName()) { 188 if (!isEventOverFolderTextRegion(currentFolder, ev)) { 189 currentFolder.dismissEditingName(); 190 return true; 191 } 192 } 193 194 if (!isEventOverFolder(currentFolder, ev)) { 195 if (isInAccessibleDrag()) { 196 // Do not close the folder if in drag and drop. 197 if (!isEventOverDropTargetBar(ev)) { 198 return true; 199 } 200 } else { 201 mLauncher.closeFolder(); 202 return true; 203 } 204 } 205 } 206 return false; 207 } 208 209 @Override onInterceptTouchEvent(MotionEvent ev)210 public boolean onInterceptTouchEvent(MotionEvent ev) { 211 int action = ev.getAction(); 212 213 if (action == MotionEvent.ACTION_DOWN) { 214 if (handleTouchDown(ev, true)) { 215 return true; 216 } 217 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 218 if (mTouchCompleteListener != null) { 219 mTouchCompleteListener.onTouchComplete(); 220 } 221 mTouchCompleteListener = null; 222 } 223 clearAllResizeFrames(); 224 return mDragController.onInterceptTouchEvent(ev); 225 } 226 227 @Override onInterceptHoverEvent(MotionEvent ev)228 public boolean onInterceptHoverEvent(MotionEvent ev) { 229 if (mLauncher == null || mLauncher.getWorkspace() == null) { 230 return false; 231 } 232 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 233 if (currentFolder == null) { 234 return false; 235 } else { 236 AccessibilityManager accessibilityManager = (AccessibilityManager) 237 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 238 if (accessibilityManager.isTouchExplorationEnabled()) { 239 final int action = ev.getAction(); 240 boolean isOverFolderOrSearchBar; 241 switch (action) { 242 case MotionEvent.ACTION_HOVER_ENTER: 243 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) || 244 (isInAccessibleDrag() && isEventOverDropTargetBar(ev)); 245 if (!isOverFolderOrSearchBar) { 246 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 247 mHoverPointClosesFolder = true; 248 return true; 249 } 250 mHoverPointClosesFolder = false; 251 break; 252 case MotionEvent.ACTION_HOVER_MOVE: 253 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) || 254 (isInAccessibleDrag() && isEventOverDropTargetBar(ev)); 255 if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) { 256 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 257 mHoverPointClosesFolder = true; 258 return true; 259 } else if (!isOverFolderOrSearchBar) { 260 return true; 261 } 262 mHoverPointClosesFolder = false; 263 } 264 } 265 } 266 return false; 267 } 268 sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)269 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { 270 AccessibilityManager accessibilityManager = (AccessibilityManager) 271 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 272 if (accessibilityManager.isEnabled()) { 273 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; 274 AccessibilityEvent event = AccessibilityEvent.obtain( 275 AccessibilityEvent.TYPE_VIEW_FOCUSED); 276 onInitializeAccessibilityEvent(event); 277 event.getText().add(getContext().getString(stringId)); 278 accessibilityManager.sendAccessibilityEvent(event); 279 } 280 } 281 isInAccessibleDrag()282 private boolean isInAccessibleDrag() { 283 LauncherAccessibilityDelegate delegate = LauncherAppState 284 .getInstance().getAccessibilityDelegate(); 285 return delegate != null && delegate.isInAccessibleDrag(); 286 } 287 288 @Override onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)289 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 290 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 291 if (currentFolder != null) { 292 if (child == currentFolder) { 293 return super.onRequestSendAccessibilityEvent(child, event); 294 } 295 296 if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) { 297 return super.onRequestSendAccessibilityEvent(child, event); 298 } 299 // Skip propagating onRequestSendAccessibilityEvent all for other children 300 // when a folder is open 301 return false; 302 } 303 return super.onRequestSendAccessibilityEvent(child, event); 304 } 305 306 @Override addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)307 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { 308 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 309 if (currentFolder != null) { 310 // Only add the folder as a child for accessibility when it is open 311 childrenForAccessibility.add(currentFolder); 312 313 if (isInAccessibleDrag()) { 314 childrenForAccessibility.add(mLauncher.getSearchDropTargetBar()); 315 } 316 } else { 317 super.addChildrenForAccessibility(childrenForAccessibility); 318 } 319 } 320 321 @Override onHoverEvent(MotionEvent ev)322 public boolean onHoverEvent(MotionEvent ev) { 323 // If we've received this, we've already done the necessary handling 324 // in onInterceptHoverEvent. Return true to consume the event. 325 return false; 326 } 327 328 @Override onTouchEvent(MotionEvent ev)329 public boolean onTouchEvent(MotionEvent ev) { 330 boolean handled = false; 331 int action = ev.getAction(); 332 333 int x = (int) ev.getX(); 334 int y = (int) ev.getY(); 335 336 if (action == MotionEvent.ACTION_DOWN) { 337 if (handleTouchDown(ev, false)) { 338 return true; 339 } 340 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 341 if (mTouchCompleteListener != null) { 342 mTouchCompleteListener.onTouchComplete(); 343 } 344 mTouchCompleteListener = null; 345 } 346 347 if (mCurrentResizeFrame != null) { 348 handled = true; 349 switch (action) { 350 case MotionEvent.ACTION_MOVE: 351 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 352 break; 353 case MotionEvent.ACTION_CANCEL: 354 case MotionEvent.ACTION_UP: 355 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 356 mCurrentResizeFrame.onTouchUp(); 357 mCurrentResizeFrame = null; 358 } 359 } 360 if (handled) return true; 361 return mDragController.onTouchEvent(ev); 362 } 363 364 /** 365 * Determine the rect of the descendant in this DragLayer's coordinates 366 * 367 * @param descendant The descendant whose coordinates we want to find. 368 * @param r The rect into which to place the results. 369 * @return The factor by which this descendant is scaled relative to this DragLayer. 370 */ getDescendantRectRelativeToSelf(View descendant, Rect r)371 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 372 mTmpXY[0] = 0; 373 mTmpXY[1] = 0; 374 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 375 376 r.set(mTmpXY[0], mTmpXY[1], 377 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()), 378 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight())); 379 return scale; 380 } 381 getLocationInDragLayer(View child, int[] loc)382 public float getLocationInDragLayer(View child, int[] loc) { 383 loc[0] = 0; 384 loc[1] = 0; 385 return getDescendantCoordRelativeToSelf(child, loc); 386 } 387 getDescendantCoordRelativeToSelf(View descendant, int[] coord)388 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 389 return getDescendantCoordRelativeToSelf(descendant, coord, false); 390 } 391 392 /** 393 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 394 * coordinates. 395 * 396 * @param descendant The descendant to which the passed coordinate is relative. 397 * @param coord The coordinate that we want mapped. 398 * @param includeRootScroll Whether or not to account for the scroll of the root descendant: 399 * sometimes this is relevant as in a child's coordinates within the root descendant. 400 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 401 * this scale factor is assumed to be equal in X and Y, and so if at any point this 402 * assumption fails, we will need to return a pair of scale factors. 403 */ getDescendantCoordRelativeToSelf(View descendant, int[] coord, boolean includeRootScroll)404 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord, 405 boolean includeRootScroll) { 406 return Utilities.getDescendantCoordRelativeToParent(descendant, this, 407 coord, includeRootScroll); 408 } 409 410 /** 411 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. 412 */ mapCoordInSelfToDescendent(View descendant, int[] coord)413 public float mapCoordInSelfToDescendent(View descendant, int[] coord) { 414 return Utilities.mapCoordInSelfToDescendent(descendant, this, coord); 415 } 416 getViewRectRelativeToSelf(View v, Rect r)417 public void getViewRectRelativeToSelf(View v, Rect r) { 418 int[] loc = new int[2]; 419 getLocationInWindow(loc); 420 int x = loc[0]; 421 int y = loc[1]; 422 423 v.getLocationInWindow(loc); 424 int vX = loc[0]; 425 int vY = loc[1]; 426 427 int left = vX - x; 428 int top = vY - y; 429 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 430 } 431 432 @Override dispatchUnhandledMove(View focused, int direction)433 public boolean dispatchUnhandledMove(View focused, int direction) { 434 return mDragController.dispatchUnhandledMove(focused, direction); 435 } 436 437 @Override generateLayoutParams(AttributeSet attrs)438 public LayoutParams generateLayoutParams(AttributeSet attrs) { 439 return new LayoutParams(getContext(), attrs); 440 } 441 442 @Override generateDefaultLayoutParams()443 protected LayoutParams generateDefaultLayoutParams() { 444 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 445 } 446 447 // Override to allow type-checking of LayoutParams. 448 @Override checkLayoutParams(ViewGroup.LayoutParams p)449 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 450 return p instanceof LayoutParams; 451 } 452 453 @Override generateLayoutParams(ViewGroup.LayoutParams p)454 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 455 return new LayoutParams(p); 456 } 457 458 public static class LayoutParams extends InsettableFrameLayout.LayoutParams { 459 public int x, y; 460 public boolean customPosition = false; 461 LayoutParams(Context c, AttributeSet attrs)462 public LayoutParams(Context c, AttributeSet attrs) { 463 super(c, attrs); 464 } 465 LayoutParams(int width, int height)466 public LayoutParams(int width, int height) { 467 super(width, height); 468 } 469 LayoutParams(ViewGroup.LayoutParams lp)470 public LayoutParams(ViewGroup.LayoutParams lp) { 471 super(lp); 472 } 473 setWidth(int width)474 public void setWidth(int width) { 475 this.width = width; 476 } 477 getWidth()478 public int getWidth() { 479 return width; 480 } 481 setHeight(int height)482 public void setHeight(int height) { 483 this.height = height; 484 } 485 getHeight()486 public int getHeight() { 487 return height; 488 } 489 setX(int x)490 public void setX(int x) { 491 this.x = x; 492 } 493 getX()494 public int getX() { 495 return x; 496 } 497 setY(int y)498 public void setY(int y) { 499 this.y = y; 500 } 501 getY()502 public int getY() { 503 return y; 504 } 505 } 506 onLayout(boolean changed, int l, int t, int r, int b)507 protected void onLayout(boolean changed, int l, int t, int r, int b) { 508 super.onLayout(changed, l, t, r, b); 509 int count = getChildCount(); 510 for (int i = 0; i < count; i++) { 511 View child = getChildAt(i); 512 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 513 if (flp instanceof LayoutParams) { 514 final LayoutParams lp = (LayoutParams) flp; 515 if (lp.customPosition) { 516 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 517 } 518 } 519 } 520 } 521 clearAllResizeFrames()522 public void clearAllResizeFrames() { 523 if (mResizeFrames.size() > 0) { 524 for (AppWidgetResizeFrame frame: mResizeFrames) { 525 frame.commitResize(); 526 removeView(frame); 527 } 528 mResizeFrames.clear(); 529 } 530 } 531 hasResizeFrames()532 public boolean hasResizeFrames() { 533 return mResizeFrames.size() > 0; 534 } 535 isWidgetBeingResized()536 public boolean isWidgetBeingResized() { 537 return mCurrentResizeFrame != null; 538 } 539 addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout)540 public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, 541 CellLayout cellLayout) { 542 AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), 543 widget, cellLayout, this); 544 545 LayoutParams lp = new LayoutParams(-1, -1); 546 lp.customPosition = true; 547 548 addView(resizeFrame, lp); 549 mResizeFrames.add(resizeFrame); 550 551 resizeFrame.snapToWidget(false); 552 } 553 animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)554 public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, 555 float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, 556 int duration) { 557 Rect r = new Rect(); 558 getViewRectRelativeToSelf(dragView, r); 559 final int fromX = r.left; 560 final int fromY = r.top; 561 562 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, 563 onFinishRunnable, animationEndStyle, duration, null); 564 } 565 animateViewIntoPosition(DragView dragView, final View child, final Runnable onFinishAnimationRunnable, View anchorView)566 public void animateViewIntoPosition(DragView dragView, final View child, 567 final Runnable onFinishAnimationRunnable, View anchorView) { 568 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView); 569 } 570 animateViewIntoPosition(DragView dragView, final View child, int duration, final Runnable onFinishAnimationRunnable, View anchorView)571 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 572 final Runnable onFinishAnimationRunnable, View anchorView) { 573 ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); 574 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 575 parentChildren.measureChild(child); 576 577 Rect r = new Rect(); 578 getViewRectRelativeToSelf(dragView, r); 579 580 int coord[] = new int[2]; 581 float childScale = child.getScaleX(); 582 coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2); 583 coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2); 584 585 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 586 // the correct coordinates (above) and use these to determine the final location 587 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 588 // We need to account for the scale of the child itself, as the above only accounts for 589 // for the scale in parents. 590 scale *= childScale; 591 int toX = coord[0]; 592 int toY = coord[1]; 593 float toScale = scale; 594 if (child instanceof TextView) { 595 TextView tv = (TextView) child; 596 // Account for the source scale of the icon (ie. from AllApps to Workspace, in which 597 // the workspace may have smaller icon bounds). 598 toScale = scale / dragView.getIntrinsicIconScaleFactor(); 599 600 // The child may be scaled (always about the center of the view) so to account for it, 601 // we have to offset the position by the scaled size. Once we do that, we can center 602 // the drag view about the scaled child view. 603 toY += Math.round(toScale * tv.getPaddingTop()); 604 toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; 605 if (dragView.getDragVisualizeOffset() != null) { 606 toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); 607 } 608 609 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 610 } else if (child instanceof FolderIcon) { 611 // Account for holographic blur padding on the drag view 612 toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop())); 613 toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2; 614 toY -= (1 - scale) * dragView.getMeasuredHeight() / 2; 615 // Center in the x coordinate about the target's drawable 616 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 617 } else { 618 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 619 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 620 - child.getMeasuredWidth()))) / 2; 621 } 622 623 final int fromX = r.left; 624 final int fromY = r.top; 625 child.setVisibility(INVISIBLE); 626 Runnable onCompleteRunnable = new Runnable() { 627 public void run() { 628 child.setVisibility(VISIBLE); 629 if (onFinishAnimationRunnable != null) { 630 onFinishAnimationRunnable.run(); 631 } 632 } 633 }; 634 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, 635 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); 636 } 637 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)638 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, 639 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, 640 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, 641 int animationEndStyle, int duration, View anchorView) { 642 Rect from = new Rect(fromX, fromY, fromX + 643 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 644 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 645 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, 646 null, null, onCompleteRunnable, animationEndStyle, anchorView); 647 } 648 649 /** 650 * This method animates a view at the end of a drag and drop animation. 651 * 652 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 653 * doesn't need to be a child of DragLayer. 654 * @param from The initial location of the view. Only the left and top parameters are used. 655 * @param to The final location of the view. Only the left and top parameters are used. This 656 * location doesn't account for scaling, and so should be centered about the desired 657 * final location (including scaling). 658 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 659 * @param finalScale The final scale of the view. The view is scaled about its center. 660 * @param duration The duration of the animation. 661 * @param motionInterpolator The interpolator to use for the location of the view. 662 * @param alphaInterpolator The interpolator to use for the alpha of the view. 663 * @param onCompleteRunnable Optional runnable to run on animation completion. 664 * @param fadeOut Whether or not to fade out the view once the animation completes. If true, 665 * the runnable will execute after the view is faded out. 666 * @param anchorView If not null, this represents the view which the animated view stays 667 * anchored to in case scrolling is currently taking place. Note: currently this is 668 * only used for the X dimension for the case of the workspace. 669 */ 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)670 public void animateView(final DragView view, final Rect from, final Rect to, 671 final float finalAlpha, final float initScaleX, final float initScaleY, 672 final float finalScaleX, final float finalScaleY, int duration, 673 final Interpolator motionInterpolator, final Interpolator alphaInterpolator, 674 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { 675 676 // Calculate the duration of the animation based on the object's distance 677 final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top); 678 final Resources res = getResources(); 679 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 680 681 // If duration < 0, this is a cue to compute the duration based on the distance 682 if (duration < 0) { 683 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 684 if (dist < maxDist) { 685 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 686 } 687 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); 688 } 689 690 // Fall back to cubic ease out interpolator for the animation if none is specified 691 TimeInterpolator interpolator = null; 692 if (alphaInterpolator == null || motionInterpolator == null) { 693 interpolator = mCubicEaseOutInterpolator; 694 } 695 696 // Animate the view 697 final float initAlpha = view.getAlpha(); 698 final float dropViewScale = view.getScaleX(); 699 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { 700 @Override 701 public void onAnimationUpdate(ValueAnimator animation) { 702 final float percent = (Float) animation.getAnimatedValue(); 703 final int width = view.getMeasuredWidth(); 704 final int height = view.getMeasuredHeight(); 705 706 float alphaPercent = alphaInterpolator == null ? percent : 707 alphaInterpolator.getInterpolation(percent); 708 float motionPercent = motionInterpolator == null ? percent : 709 motionInterpolator.getInterpolation(percent); 710 711 float initialScaleX = initScaleX * dropViewScale; 712 float initialScaleY = initScaleY * dropViewScale; 713 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); 714 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); 715 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); 716 717 float fromLeft = from.left + (initialScaleX - 1f) * width / 2; 718 float fromTop = from.top + (initialScaleY - 1f) * height / 2; 719 720 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); 721 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); 722 723 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() * 724 (mAnchorViewInitialScrollX - mAnchorView.getScrollX())); 725 726 int xPos = x - mDropView.getScrollX() + anchorAdjust; 727 int yPos = y - mDropView.getScrollY(); 728 729 mDropView.setTranslationX(xPos); 730 mDropView.setTranslationY(yPos); 731 mDropView.setScaleX(scaleX); 732 mDropView.setScaleY(scaleY); 733 mDropView.setAlpha(alpha); 734 } 735 }; 736 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, 737 anchorView); 738 } 739 animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)740 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, 741 TimeInterpolator interpolator, final Runnable onCompleteRunnable, 742 final int animationEndStyle, View anchorView) { 743 // Clean up the previous animations 744 if (mDropAnim != null) mDropAnim.cancel(); 745 746 // Show the drop view if it was previously hidden 747 mDropView = view; 748 mDropView.cancelAnimation(); 749 mDropView.resetLayoutParams(); 750 751 // Set the anchor view if the page is scrolling 752 if (anchorView != null) { 753 mAnchorViewInitialScrollX = anchorView.getScrollX(); 754 } 755 mAnchorView = anchorView; 756 757 // Create and start the animation 758 mDropAnim = new ValueAnimator(); 759 mDropAnim.setInterpolator(interpolator); 760 mDropAnim.setDuration(duration); 761 mDropAnim.setFloatValues(0f, 1f); 762 mDropAnim.addUpdateListener(updateCb); 763 mDropAnim.addListener(new AnimatorListenerAdapter() { 764 public void onAnimationEnd(Animator animation) { 765 if (onCompleteRunnable != null) { 766 onCompleteRunnable.run(); 767 } 768 switch (animationEndStyle) { 769 case ANIMATION_END_DISAPPEAR: 770 clearAnimatedView(); 771 break; 772 case ANIMATION_END_REMAIN_VISIBLE: 773 break; 774 } 775 } 776 }); 777 mDropAnim.start(); 778 } 779 clearAnimatedView()780 public void clearAnimatedView() { 781 if (mDropAnim != null) { 782 mDropAnim.cancel(); 783 } 784 if (mDropView != null) { 785 mDragController.onDeferredEndDrag(mDropView); 786 } 787 mDropView = null; 788 invalidate(); 789 } 790 getAnimatedView()791 public View getAnimatedView() { 792 return mDropView; 793 } 794 795 @Override onChildViewAdded(View parent, View child)796 public void onChildViewAdded(View parent, View child) { 797 super.onChildViewAdded(parent, child); 798 if (mOverlayView != null) { 799 // ensure that the overlay view stays on top. we can't use drawing order for this 800 // because in API level 16 touch dispatch doesn't respect drawing order. 801 mOverlayView.bringToFront(); 802 } 803 updateChildIndices(); 804 } 805 806 @Override onChildViewRemoved(View parent, View child)807 public void onChildViewRemoved(View parent, View child) { 808 updateChildIndices(); 809 } 810 811 @Override bringChildToFront(View child)812 public void bringChildToFront(View child) { 813 super.bringChildToFront(child); 814 if (child != mOverlayView && mOverlayView != null) { 815 // ensure that the overlay view stays on top. we can't use drawing order for this 816 // because in API level 16 touch dispatch doesn't respect drawing order. 817 mOverlayView.bringToFront(); 818 } 819 updateChildIndices(); 820 } 821 updateChildIndices()822 private void updateChildIndices() { 823 mTopViewIndex = -1; 824 int childCount = getChildCount(); 825 for (int i = 0; i < childCount; i++) { 826 if (getChildAt(i) instanceof DragView) { 827 mTopViewIndex = i; 828 } 829 } 830 mChildCountOnLastUpdate = childCount; 831 } 832 833 @Override getChildDrawingOrder(int childCount, int i)834 protected int getChildDrawingOrder(int childCount, int i) { 835 if (mChildCountOnLastUpdate != childCount) { 836 // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed. 837 // Pre-18, the child was not added / removed by the time of those callbacks. We need to 838 // force update our representation of things here to avoid crashing on pre-18 devices 839 // in certain instances. 840 updateChildIndices(); 841 } 842 843 // i represents the current draw iteration 844 if (mTopViewIndex == -1) { 845 // in general we do nothing 846 return i; 847 } else if (i == childCount - 1) { 848 // if we have a top index, we return it when drawing last item (highest z-order) 849 return mTopViewIndex; 850 } else if (i < mTopViewIndex) { 851 return i; 852 } else { 853 // for indexes greater than the top index, we fetch one item above to shift for the 854 // displacement of the top index 855 return i + 1; 856 } 857 } 858 onEnterScrollArea(int direction)859 void onEnterScrollArea(int direction) { 860 mInScrollArea = true; 861 invalidate(); 862 } 863 onExitScrollArea()864 void onExitScrollArea() { 865 mInScrollArea = false; 866 invalidate(); 867 } 868 showPageHints()869 void showPageHints() { 870 mShowPageHints = true; 871 Workspace workspace = mLauncher.getWorkspace(); 872 getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()), 873 mScrollChildPosition); 874 invalidate(); 875 } 876 hidePageHints()877 void hidePageHints() { 878 mShowPageHints = false; 879 invalidate(); 880 } 881 882 @Override dispatchDraw(Canvas canvas)883 protected void dispatchDraw(Canvas canvas) { 884 // Draw the background below children. 885 if (mBackgroundAlpha > 0.0f) { 886 int alpha = (int) (mBackgroundAlpha * 255); 887 canvas.drawColor((alpha << 24) | SCRIM_COLOR); 888 } 889 890 super.dispatchDraw(canvas); 891 } 892 drawPageHints(Canvas canvas)893 private void drawPageHints(Canvas canvas) { 894 if (mShowPageHints) { 895 Workspace workspace = mLauncher.getWorkspace(); 896 int width = getMeasuredWidth(); 897 int page = workspace.getNextPage(); 898 CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1); 899 CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1); 900 901 if (leftPage != null && leftPage.isDragTarget()) { 902 Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ? 903 mLeftHoverDrawableActive : mLeftHoverDrawable; 904 left.setBounds(0, mScrollChildPosition.top, 905 left.getIntrinsicWidth(), mScrollChildPosition.bottom); 906 left.draw(canvas); 907 } 908 if (rightPage != null && rightPage.isDragTarget()) { 909 Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ? 910 mRightHoverDrawableActive : mRightHoverDrawable; 911 right.setBounds(width - right.getIntrinsicWidth(), 912 mScrollChildPosition.top, width, mScrollChildPosition.bottom); 913 right.draw(canvas); 914 } 915 } 916 } 917 drawChild(Canvas canvas, View child, long drawingTime)918 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 919 boolean ret = super.drawChild(canvas, child, drawingTime); 920 921 // We want to draw the page hints above the workspace, but below the drag view. 922 if (child instanceof Workspace) { 923 drawPageHints(canvas); 924 } 925 return ret; 926 } 927 setBackgroundAlpha(float alpha)928 public void setBackgroundAlpha(float alpha) { 929 if (alpha != mBackgroundAlpha) { 930 mBackgroundAlpha = alpha; 931 invalidate(); 932 } 933 } 934 getBackgroundAlpha()935 public float getBackgroundAlpha() { 936 return mBackgroundAlpha; 937 } 938 setTouchCompleteListener(TouchCompleteListener listener)939 public void setTouchCompleteListener(TouchCompleteListener listener) { 940 mTouchCompleteListener = listener; 941 } 942 943 public interface TouchCompleteListener { onTouchComplete()944 public void onTouchComplete(); 945 } 946 } 947