DragLayer.java revision 716b51e030f9c6ed34af2b947760e46a280c65a6
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.TimeInterpolator; 22import android.animation.ValueAnimator; 23import android.animation.ValueAnimator.AnimatorUpdateListener; 24import android.content.Context; 25import android.content.res.Resources; 26import android.graphics.Bitmap; 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.widget.FrameLayout; 36import android.widget.ImageView; 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 TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f); 60 private int[] mDropViewPos = new int[] { -1, -1 }; 61 private View mDropView = null; 62 63 /** 64 * Used to create a new DragLayer from XML. 65 * 66 * @param context The application's context. 67 * @param attrs The attributes set containing the Workspace's customization values. 68 */ 69 public DragLayer(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 72 // Disable multitouch across the workspace/all apps/customize tray 73 setMotionEventSplittingEnabled(false); 74 } 75 76 public void setup(Launcher launcher, DragController controller) { 77 mLauncher = launcher; 78 mDragController = controller; 79 } 80 81 @Override 82 public boolean dispatchKeyEvent(KeyEvent event) { 83 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 84 } 85 86 private boolean handleTouchDown(MotionEvent ev, boolean intercept) { 87 Rect hitRect = new Rect(); 88 int x = (int) ev.getX(); 89 int y = (int) ev.getY(); 90 91 for (AppWidgetResizeFrame child: mResizeFrames) { 92 child.getHitRect(hitRect); 93 if (hitRect.contains(x, y)) { 94 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { 95 mCurrentResizeFrame = child; 96 mXDown = x; 97 mYDown = y; 98 requestDisallowInterceptTouchEvent(true); 99 return true; 100 } 101 } 102 } 103 104 Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); 105 if (currentFolder != null && intercept) { 106 if (currentFolder.isEditingName()) { 107 getDescendantRectRelativeToSelf(currentFolder.getEditTextRegion(), hitRect); 108 if (!hitRect.contains(x, y)) { 109 currentFolder.dismissEditingName(); 110 return true; 111 } 112 } 113 114 getDescendantRectRelativeToSelf(currentFolder, hitRect); 115 if (!hitRect.contains(x, y)) { 116 mLauncher.closeFolder(); 117 return true; 118 } 119 } 120 return false; 121 } 122 123 @Override 124 public boolean onInterceptTouchEvent(MotionEvent ev) { 125 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 126 if (handleTouchDown(ev, true)) { 127 return true; 128 } 129 } 130 clearAllResizeFrames(); 131 return mDragController.onInterceptTouchEvent(ev); 132 } 133 134 @Override 135 public boolean onTouchEvent(MotionEvent ev) { 136 boolean handled = false; 137 int action = ev.getAction(); 138 139 int x = (int) ev.getX(); 140 int y = (int) ev.getY(); 141 142 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 143 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 144 if (handleTouchDown(ev, false)) { 145 return true; 146 } 147 } 148 } 149 150 if (mCurrentResizeFrame != null) { 151 handled = true; 152 switch (action) { 153 case MotionEvent.ACTION_MOVE: 154 mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); 155 break; 156 case MotionEvent.ACTION_CANCEL: 157 case MotionEvent.ACTION_UP: 158 mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown); 159 mCurrentResizeFrame = null; 160 } 161 } 162 if (handled) return true; 163 return mDragController.onTouchEvent(ev); 164 } 165 166 public void getDescendantRectRelativeToSelf(View descendant, Rect r) { 167 descendant.getHitRect(r); 168 mTmpXY[0] = 0; 169 mTmpXY[1] = 0; 170 getDescendantCoordRelativeToSelf(descendant, mTmpXY); 171 r.offset(mTmpXY[0], mTmpXY[1]); 172 } 173 174 public void getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 175 ViewParent viewParent = descendant.getParent(); 176 while (viewParent instanceof View && viewParent != this) { 177 final View view = (View)viewParent; 178 coord[0] += view.getLeft() + (int) (view.getTranslationX() + 0.5f) - view.getScrollX(); 179 coord[1] += view.getTop() + (int) (view.getTranslationY() + 0.5f) - view.getScrollY(); 180 viewParent = view.getParent(); 181 } 182 } 183 184 public void getViewLocationRelativeToSelf(View v, int[] location) { 185 getLocationOnScreen(location); 186 int x = location[0]; 187 int y = location[1]; 188 189 v.getLocationOnScreen(location); 190 int vX = location[0]; 191 int vY = location[1]; 192 193 location[0] = vX - x; 194 location[1] = vY - y; 195 } 196 197 @Override 198 public boolean dispatchUnhandledMove(View focused, int direction) { 199 return mDragController.dispatchUnhandledMove(focused, direction); 200 } 201 202 public View createDragView(Bitmap b, int xPos, int yPos) { 203 ImageView imageView = new ImageView(mContext); 204 imageView.setImageBitmap(b); 205 imageView.setX(xPos); 206 imageView.setY(yPos); 207 addView(imageView, b.getWidth(), b.getHeight()); 208 209 return imageView; 210 } 211 212 public View createDragView(View v) { 213 v.getLocationOnScreen(mTmpXY); 214 return createDragView(mDragController.getViewBitmap(v), mTmpXY[0], mTmpXY[1]); 215 } 216 217 public static class LayoutParams extends FrameLayout.LayoutParams { 218 public int x, y; 219 public boolean customPosition = false; 220 221 /** 222 * {@inheritDoc} 223 */ 224 public LayoutParams(int width, int height) { 225 super(width, height); 226 } 227 228 public void setWidth(int width) { 229 this.width = width; 230 } 231 232 public int getWidth() { 233 return width; 234 } 235 236 public void setHeight(int height) { 237 this.height = height; 238 } 239 240 public int getHeight() { 241 return height; 242 } 243 244 public void setX(int x) { 245 this.x = x; 246 } 247 248 public int getX() { 249 return x; 250 } 251 252 public void setY(int y) { 253 this.y = y; 254 } 255 256 public int getY() { 257 return y; 258 } 259 } 260 261 protected void onLayout(boolean changed, int l, int t, int r, int b) { 262 super.onLayout(changed, l, t, r, b); 263 int count = getChildCount(); 264 for (int i = 0; i < count; i++) { 265 View child = getChildAt(i); 266 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 267 if (flp instanceof LayoutParams) { 268 final LayoutParams lp = (LayoutParams) flp; 269 if (lp.customPosition) { 270 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 271 } 272 } 273 } 274 } 275 276 public void clearAllResizeFrames() { 277 if (mResizeFrames.size() > 0) { 278 for (AppWidgetResizeFrame frame: mResizeFrames) { 279 removeView(frame); 280 } 281 mResizeFrames.clear(); 282 } 283 } 284 285 public boolean hasResizeFrames() { 286 return mResizeFrames.size() > 0; 287 } 288 289 public boolean isWidgetBeingResized() { 290 return mCurrentResizeFrame != null; 291 } 292 293 public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, 294 CellLayout cellLayout) { 295 AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), 296 itemInfo, widget, cellLayout, this); 297 298 LayoutParams lp = new LayoutParams(-1, -1); 299 lp.customPosition = true; 300 301 addView(resizeFrame, lp); 302 mResizeFrames.add(resizeFrame); 303 304 resizeFrame.snapToWidget(false); 305 } 306 307 public void animateViewIntoPosition(DragView dragView, final View child) { 308 ((CellLayoutChildren) child.getParent()).measureChild(child); 309 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 310 311 int[] loc = new int[2]; 312 getViewLocationRelativeToSelf(dragView, loc); 313 314 int coord[] = new int[2]; 315 coord[0] = lp.x; 316 coord[1] = lp.y; 317 getDescendantCoordRelativeToSelf(child, coord); 318 319 final int fromX = loc[0] + (dragView.getWidth() - child.getMeasuredWidth()) / 2; 320 final int fromY = loc[1] + (dragView.getHeight() - child.getMeasuredHeight()) / 2; 321 final int dx = coord[0] - fromX; 322 final int dy = coord[1] - fromY; 323 324 child.setVisibility(INVISIBLE); 325 animateViewIntoPosition(child, fromX, fromY, dx, dy); 326 } 327 328 private void animateViewIntoPosition(final View view, final int fromX, final int fromY, 329 final int dX, final int dY) { 330 331 // Calculate the duration of the animation based on the object's distance 332 final float dist = (float) Math.sqrt(dX*dX + dY*dY); 333 final Resources res = getResources(); 334 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 335 int duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 336 if (dist < maxDist) { 337 duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist); 338 } 339 340 if (mDropAnim != null) { 341 mDropAnim.end(); 342 } 343 mDropAnim = new ValueAnimator(); 344 mDropAnim.setInterpolator(mQuintEaseOutInterpolator); 345 346 // The view is invisible during the animation; we render it manually. 347 mDropAnim.addListener(new AnimatorListenerAdapter() { 348 public void onAnimationStart(Animator animation) { 349 // Set this here so that we don't render it until the animation begins 350 mDropView = view; 351 } 352 353 public void onAnimationEnd(Animator animation) { 354 if (mDropView != null) { 355 mDropView.setVisibility(View.VISIBLE); 356 mDropView = null; 357 } 358 } 359 }); 360 361 mDropAnim.setDuration(duration); 362 mDropAnim.setFloatValues(0.0f, 1.0f); 363 mDropAnim.removeAllUpdateListeners(); 364 mDropAnim.addUpdateListener(new AnimatorUpdateListener() { 365 public void onAnimationUpdate(ValueAnimator animation) { 366 final float percent = (Float) animation.getAnimatedValue(); 367 // Invalidate the old position 368 int width = view.getMeasuredWidth(); 369 int height = view.getMeasuredHeight(); 370 invalidate(mDropViewPos[0], mDropViewPos[1], 371 mDropViewPos[0] + width, mDropViewPos[1] + height); 372 373 mDropViewPos[0] = fromX + (int) (percent * dX + 0.5f); 374 mDropViewPos[1] = fromY + (int) (percent * dY + 0.5f); 375 invalidate(mDropViewPos[0], mDropViewPos[1], 376 mDropViewPos[0] + width, mDropViewPos[1] + height); 377 } 378 }); 379 mDropAnim.start(); 380 } 381 382 @Override 383 protected void dispatchDraw(Canvas canvas) { 384 super.dispatchDraw(canvas); 385 if (mDropView != null) { 386 // We are animating an item that was just dropped on the home screen. 387 // Render its View in the current animation position. 388 canvas.save(Canvas.MATRIX_SAVE_FLAG); 389 final int xPos = mDropViewPos[0] - mDropView.getScrollX(); 390 final int yPos = mDropViewPos[1] - mDropView.getScrollY(); 391 canvas.translate(xPos, yPos); 392 mDropView.draw(canvas); 393 canvas.restore(); 394 } 395 } 396} 397