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