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