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