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