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.launcher2; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.animation.ValueAnimator; 24 import android.animation.ValueAnimator.AnimatorUpdateListener; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.graphics.Canvas; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.util.AttributeSet; 31 import android.view.KeyEvent; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.view.ViewParent; 35 import android.view.animation.DecelerateInterpolator; 36 import android.view.animation.Interpolator; 37 import android.widget.FrameLayout; 38 import android.widget.TextView; 39 40 import com.android.launcher.R; 41 42 import java.util.ArrayList; 43 44 /** 45 * A ViewGroup that coordinates dragging across its descendants 46 */ 47 public class DragLayer extends FrameLayout { 48 private DragController mDragController; 49 private int[] mTmpXY = new int[2]; 50 51 private int mXDown, mYDown; 52 private Launcher mLauncher; 53 54 // Variables relating to resizing widgets 55 private final ArrayList<AppWidgetResizeFrame> mResizeFrames = 56 new ArrayList<AppWidgetResizeFrame>(); 57 private AppWidgetResizeFrame mCurrentResizeFrame; 58 59 // Variables relating to animation of views after drop 60 private ValueAnimator mDropAnim = null; 61 private ValueAnimator mFadeOutAnim = null; 62 private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 63 private View mDropView = null; 64 65 private int[] mDropViewPos = new int[2]; 66 private float mDropViewScale; 67 private float mDropViewAlpha; 68 69 /** 70 * Used to create a new DragLayer from XML. 71 * 72 * @param context The application's context. 73 * @param attrs The attributes set containing the Workspace's customization values. 74 */ DragLayer(Context context, AttributeSet attrs)75 public DragLayer(Context context, AttributeSet attrs) { 76 super(context, attrs); 77 78 // Disable multitouch across the workspace/all apps/customize tray 79 setMotionEventSplittingEnabled(false); 80 } 81 setup(Launcher launcher, DragController controller)82 public void setup(Launcher launcher, DragController controller) { 83 mLauncher = launcher; 84 mDragController = controller; 85 } 86 87 @Override dispatchKeyEvent(KeyEvent event)88 public boolean dispatchKeyEvent(KeyEvent event) { 89 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 90 } 91 handleTouchDown(MotionEvent ev, boolean intercept)92 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 93 Rect hitRect = new Rect(); 94 int x = (int) ev.getX(); 95 int y = (int) ev.getY(); 96 97 for (AppWidgetResizeFrame child: mResizeFrames) { 98 child.getHitRect(hitRect); 99 if (hitRect.contains(x, y)) { 100 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 101 mCurrentResizeFrame = child; 102 mXDown = x; 103 mYDown = y; 104 requestDisallowInterceptTouchEvent(true); 105 return true; 106 } 107 } 108 } 109 110 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 111 if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) { 112 if (currentFolder.isEditingName()) { 113 getDescendantRectRelativeToSelf(currentFolder.getEditTextRegion(), hitRect); 114 if (!hitRect.contains(x, y)) { 115 currentFolder.dismissEditingName(); 116 return true; 117 } 118 } 119 120 getDescendantRectRelativeToSelf(currentFolder, hitRect); 121 if (!hitRect.contains(x, y)) { 122 mLauncher.closeFolder(); 123 return true; 124 } 125 } 126 return false; 127 } 128 129 @Override onInterceptTouchEvent(MotionEvent ev)130 public boolean onInterceptTouchEvent(MotionEvent ev) { 131 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 132 if (handleTouchDown(ev, true)) { 133 return true; 134 } 135 } 136 clearAllResizeFrames(); 137 return mDragController.onInterceptTouchEvent(ev); 138 } 139 140 @Override onTouchEvent(MotionEvent ev)141 public boolean onTouchEvent(MotionEvent ev) { 142 boolean handled = false; 143 int action = ev.getAction(); 144 145 int x = (int) ev.getX(); 146 int y = (int) ev.getY(); 147 148 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 149 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 150 if (handleTouchDown(ev, false)) { 151 return true; 152 } 153 } 154 } 155 156 if (mCurrentResizeFrame != null) { 157 handled = true; 158 switch (action) { 159 case MotionEvent.ACTION_MOVE: 160 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 161 break; 162 case MotionEvent.ACTION_CANCEL: 163 case MotionEvent.ACTION_UP: 164 mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown); 165 mCurrentResizeFrame = null; 166 } 167 } 168 if (handled) return true; 169 return mDragController.onTouchEvent(ev); 170 } 171 172 /** 173 * Determine the rect of the descendant in this DragLayer's coordinates 174 * 175 * @param descendant The descendant whose coordinates we want to find. 176 * @param r The rect into which to place the results. 177 * @return The factor by which this descendant is scaled relative to this DragLayer. 178 */ getDescendantRectRelativeToSelf(View descendant, Rect r)179 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 180 mTmpXY[0] = 0; 181 mTmpXY[1] = 0; 182 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 183 r.set(mTmpXY[0], mTmpXY[1], 184 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); 185 return scale; 186 } 187 getLocationInDragLayer(View child, int[] loc)188 public void getLocationInDragLayer(View child, int[] loc) { 189 loc[0] = 0; 190 loc[1] = 0; 191 getDescendantCoordRelativeToSelf(child, loc); 192 } 193 194 /** 195 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 196 * coordinates. 197 * 198 * @param descendant The descendant to which the passed coordinate is relative. 199 * @param coord The coordinate that we want mapped. 200 * @return The factor by which this descendant is scaled relative to this DragLayer. 201 */ getDescendantCoordRelativeToSelf(View descendant, int[] coord)202 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 203 float scale = 1.0f; 204 float[] pt = {coord[0], coord[1]}; 205 descendant.getMatrix().mapPoints(pt); 206 scale *= descendant.getScaleX(); 207 pt[0] += descendant.getLeft(); 208 pt[1] += descendant.getTop(); 209 ViewParent viewParent = descendant.getParent(); 210 while (viewParent instanceof View && viewParent != this) { 211 final View view = (View)viewParent; 212 view.getMatrix().mapPoints(pt); 213 scale *= view.getScaleX(); 214 pt[0] += view.getLeft() - view.getScrollX(); 215 pt[1] += view.getTop() - view.getScrollY(); 216 viewParent = view.getParent(); 217 } 218 coord[0] = (int) Math.round(pt[0]); 219 coord[1] = (int) Math.round(pt[1]); 220 return scale; 221 } 222 getViewRectRelativeToSelf(View v, Rect r)223 public void getViewRectRelativeToSelf(View v, Rect r) { 224 int[] loc = new int[2]; 225 getLocationInWindow(loc); 226 int x = loc[0]; 227 int y = loc[1]; 228 229 v.getLocationInWindow(loc); 230 int vX = loc[0]; 231 int vY = loc[1]; 232 233 int left = vX - x; 234 int top = vY - y; 235 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 236 } 237 238 @Override dispatchUnhandledMove(View focused, int direction)239 public boolean dispatchUnhandledMove(View focused, int direction) { 240 return mDragController.dispatchUnhandledMove(focused, direction); 241 } 242 243 public static class LayoutParams extends FrameLayout.LayoutParams { 244 public int x, y; 245 public boolean customPosition = false; 246 247 /** 248 * {@inheritDoc} 249 */ LayoutParams(int width, int height)250 public LayoutParams(int width, int height) { 251 super(width, height); 252 } 253 setWidth(int width)254 public void setWidth(int width) { 255 this.width = width; 256 } 257 getWidth()258 public int getWidth() { 259 return width; 260 } 261 setHeight(int height)262 public void setHeight(int height) { 263 this.height = height; 264 } 265 getHeight()266 public int getHeight() { 267 return height; 268 } 269 setX(int x)270 public void setX(int x) { 271 this.x = x; 272 } 273 getX()274 public int getX() { 275 return x; 276 } 277 setY(int y)278 public void setY(int y) { 279 this.y = y; 280 } 281 getY()282 public int getY() { 283 return y; 284 } 285 } 286 onLayout(boolean changed, int l, int t, int r, int b)287 protected void onLayout(boolean changed, int l, int t, int r, int b) { 288 super.onLayout(changed, l, t, r, b); 289 int count = getChildCount(); 290 for (int i = 0; i < count; i++) { 291 View child = getChildAt(i); 292 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 293 if (flp instanceof LayoutParams) { 294 final LayoutParams lp = (LayoutParams) flp; 295 if (lp.customPosition) { 296 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 297 } 298 } 299 } 300 } 301 clearAllResizeFrames()302 public void clearAllResizeFrames() { 303 if (mResizeFrames.size() > 0) { 304 for (AppWidgetResizeFrame frame: mResizeFrames) { 305 removeView(frame); 306 } 307 mResizeFrames.clear(); 308 } 309 } 310 hasResizeFrames()311 public boolean hasResizeFrames() { 312 return mResizeFrames.size() > 0; 313 } 314 isWidgetBeingResized()315 public boolean isWidgetBeingResized() { 316 return mCurrentResizeFrame != null; 317 } 318 addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout)319 public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, 320 CellLayout cellLayout) { 321 AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), 322 itemInfo, widget, cellLayout, this); 323 324 LayoutParams lp = new LayoutParams(-1, -1); 325 lp.customPosition = true; 326 327 addView(resizeFrame, lp); 328 mResizeFrames.add(resizeFrame); 329 330 resizeFrame.snapToWidget(false); 331 } 332 animateViewIntoPosition(DragView dragView, final View child)333 public void animateViewIntoPosition(DragView dragView, final View child) { 334 animateViewIntoPosition(dragView, child, null); 335 } 336 animateViewIntoPosition(DragView dragView, final int[] pos, float scale, Runnable onFinishRunnable)337 public void animateViewIntoPosition(DragView dragView, final int[] pos, float scale, 338 Runnable onFinishRunnable) { 339 Rect r = new Rect(); 340 getViewRectRelativeToSelf(dragView, r); 341 final int fromX = r.left; 342 final int fromY = r.top; 343 344 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], scale, 345 onFinishRunnable, true, -1); 346 } 347 animateViewIntoPosition(DragView dragView, final View child, final Runnable onFinishAnimationRunnable)348 public void animateViewIntoPosition(DragView dragView, final View child, 349 final Runnable onFinishAnimationRunnable) { 350 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable); 351 } 352 animateViewIntoPosition(DragView dragView, final View child, int duration, final Runnable onFinishAnimationRunnable)353 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 354 final Runnable onFinishAnimationRunnable) { 355 ((CellLayoutChildren) child.getParent()).measureChild(child); 356 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 357 358 Rect r = new Rect(); 359 getViewRectRelativeToSelf(dragView, r); 360 361 int coord[] = new int[2]; 362 coord[0] = lp.x; 363 coord[1] = lp.y; 364 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 365 // the correct coordinates (above) and use these to determine the final location 366 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 367 int toX = coord[0]; 368 int toY = coord[1]; 369 if (child instanceof TextView) { 370 TextView tv = (TextView) child; 371 Drawable d = tv.getCompoundDrawables()[1]; 372 373 // Center in the y coordinate about the target's drawable 374 toY += Math.round(scale * tv.getPaddingTop()); 375 toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2; 376 // Center in the x coordinate about the target's drawable 377 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 378 } else if (child instanceof FolderIcon) { 379 // Account for holographic blur padding on the drag view 380 toY -= HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2; 381 // Center in the x coordinate about the target's drawable 382 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 383 } else { 384 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 385 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 386 - child.getMeasuredWidth()))) / 2; 387 } 388 389 final int fromX = r.left; 390 final int fromY = r.top; 391 child.setVisibility(INVISIBLE); 392 child.setAlpha(0); 393 Runnable onCompleteRunnable = new Runnable() { 394 public void run() { 395 child.setVisibility(VISIBLE); 396 ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f); 397 oa.setDuration(60); 398 oa.addListener(new AnimatorListenerAdapter() { 399 @Override 400 public void onAnimationEnd(android.animation.Animator animation) { 401 if (onFinishAnimationRunnable != null) { 402 onFinishAnimationRunnable.run(); 403 } 404 } 405 }); 406 oa.start(); 407 } 408 }; 409 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, scale, 410 onCompleteRunnable, true, duration); 411 } 412 animateViewIntoPosition(final View view, final int fromX, final int fromY, final int toX, final int toY, float finalScale, Runnable onCompleteRunnable, boolean fadeOut, int duration)413 private void animateViewIntoPosition(final View view, final int fromX, final int fromY, 414 final int toX, final int toY, float finalScale, Runnable onCompleteRunnable, 415 boolean fadeOut, int duration) { 416 Rect from = new Rect(fromX, fromY, fromX + 417 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 418 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 419 animateView(view, from, to, 1f, finalScale, duration, null, null, onCompleteRunnable, true); 420 } 421 422 /** 423 * This method animates a view at the end of a drag and drop animation. 424 * 425 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 426 * doesn't need to be a child of DragLayer. 427 * @param from The initial location of the view. Only the left and top parameters are used. 428 * @param to The final location of the view. Only the left and top parameters are used. This 429 * location doesn't account for scaling, and so should be centered about the desired 430 * final location (including scaling). 431 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 432 * @param finalScale The final scale of the view. The view is scaled about its center. 433 * @param duration The duration of the animation. 434 * @param motionInterpolator The interpolator to use for the location of the view. 435 * @param alphaInterpolator The interpolator to use for the alpha of the view. 436 * @param onCompleteRunnable Optional runnable to run on animation completion. 437 * @param fadeOut Whether or not to fade out the view once the animation completes. If true, 438 * the runnable will execute after the view is faded out. 439 */ animateView(final View view, final Rect from, final Rect to, final float finalAlpha, final float finalScale, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final boolean fadeOut)440 public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha, 441 final float finalScale, int duration, final Interpolator motionInterpolator, 442 final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, 443 final boolean fadeOut) { 444 // Calculate the duration of the animation based on the object's distance 445 final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + 446 Math.pow(to.top - from.top, 2)); 447 final Resources res = getResources(); 448 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 449 450 // If duration < 0, this is a cue to compute the duration based on the distance 451 if (duration < 0) { 452 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 453 if (dist < maxDist) { 454 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 455 } 456 } 457 458 if (mDropAnim != null) { 459 mDropAnim.cancel(); 460 } 461 462 if (mFadeOutAnim != null) { 463 mFadeOutAnim.cancel(); 464 } 465 466 mDropView = view; 467 final float initialAlpha = view.getAlpha(); 468 mDropAnim = new ValueAnimator(); 469 if (alphaInterpolator == null || motionInterpolator == null) { 470 mDropAnim.setInterpolator(mCubicEaseOutInterpolator); 471 } 472 473 mDropAnim.setDuration(duration); 474 mDropAnim.setFloatValues(0.0f, 1.0f); 475 mDropAnim.removeAllUpdateListeners(); 476 mDropAnim.addUpdateListener(new AnimatorUpdateListener() { 477 public void onAnimationUpdate(ValueAnimator animation) { 478 final float percent = (Float) animation.getAnimatedValue(); 479 // Invalidate the old position 480 int width = view.getMeasuredWidth(); 481 int height = view.getMeasuredHeight(); 482 invalidate(mDropViewPos[0], mDropViewPos[1], 483 mDropViewPos[0] + width, mDropViewPos[1] + height); 484 485 float alphaPercent = alphaInterpolator == null ? percent : 486 alphaInterpolator.getInterpolation(percent); 487 float motionPercent = motionInterpolator == null ? percent : 488 motionInterpolator.getInterpolation(percent); 489 490 mDropViewPos[0] = from.left + (int) Math.round(((to.left - from.left) * motionPercent)); 491 mDropViewPos[1] = from.top + (int) Math.round(((to.top - from.top) * motionPercent)); 492 mDropViewScale = percent * finalScale + (1 - percent); 493 mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha; 494 invalidate(mDropViewPos[0], mDropViewPos[1], 495 mDropViewPos[0] + width, mDropViewPos[1] + height); 496 } 497 }); 498 mDropAnim.addListener(new AnimatorListenerAdapter() { 499 public void onAnimationEnd(Animator animation) { 500 if (onCompleteRunnable != null) { 501 onCompleteRunnable.run(); 502 } 503 if (fadeOut) { 504 fadeOutDragView(); 505 } else { 506 mDropView = null; 507 } 508 } 509 }); 510 mDropAnim.start(); 511 } 512 fadeOutDragView()513 private void fadeOutDragView() { 514 mFadeOutAnim = new ValueAnimator(); 515 mFadeOutAnim.setDuration(150); 516 mFadeOutAnim.setFloatValues(0f, 1f); 517 mFadeOutAnim.removeAllUpdateListeners(); 518 mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() { 519 public void onAnimationUpdate(ValueAnimator animation) { 520 final float percent = (Float) animation.getAnimatedValue(); 521 mDropViewAlpha = 1 - percent; 522 int width = mDropView.getMeasuredWidth(); 523 int height = mDropView.getMeasuredHeight(); 524 invalidate(mDropViewPos[0], mDropViewPos[1], 525 mDropViewPos[0] + width, mDropViewPos[1] + height); 526 } 527 }); 528 mFadeOutAnim.addListener(new AnimatorListenerAdapter() { 529 public void onAnimationEnd(Animator animation) { 530 mDropView = null; 531 } 532 }); 533 mFadeOutAnim.start(); 534 } 535 536 @Override dispatchDraw(Canvas canvas)537 protected void dispatchDraw(Canvas canvas) { 538 super.dispatchDraw(canvas); 539 if (mDropView != null) { 540 // We are animating an item that was just dropped on the home screen. 541 // Render its View in the current animation position. 542 canvas.save(Canvas.MATRIX_SAVE_FLAG); 543 final int xPos = mDropViewPos[0] - mDropView.getScrollX(); 544 final int yPos = mDropViewPos[1] - mDropView.getScrollY(); 545 int width = mDropView.getMeasuredWidth(); 546 int height = mDropView.getMeasuredHeight(); 547 canvas.translate(xPos, yPos); 548 canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2); 549 canvas.scale(mDropViewScale, mDropViewScale); 550 mDropView.setAlpha(mDropViewAlpha); 551 mDropView.draw(canvas); 552 canvas.restore(); 553 } 554 } 555 } 556