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