blob: aa6615a22a3bebcf35bddc6ad80f61f4371047ca [file] [log] [blame]
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001/*
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.launcher;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Matrix;
23import android.graphics.Rect;
24import android.graphics.RectF;
25import android.graphics.Paint;
26import android.graphics.PorterDuffColorFilter;
27import android.graphics.PorterDuff;
28import android.os.Vibrator;
29import android.os.SystemClock;
30import android.util.AttributeSet;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.KeyEvent;
35import android.widget.FrameLayout;
36
37/**
38 * A ViewGroup that coordinated dragging across its dscendants
39 */
40public class DragLayer extends FrameLayout implements DragController {
41 private static final int SCROLL_DELAY = 600;
42 private static final int SCROLL_ZONE = 20;
43 private static final int VIBRATE_DURATION = 35;
44 private static final int ANIMATION_SCALE_UP_DURATION = 110;
45
46 private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
47
48 // Number of pixels to add to the dragged item for scaling
49 private static final float DRAG_SCALE = 24.0f;
50
51 private boolean mDragging = false;
52 private boolean mShouldDrop;
53 private float mLastMotionX;
54 private float mLastMotionY;
55
56 /**
57 * The bitmap that is currently being dragged
58 */
59 private Bitmap mDragBitmap = null;
60 private View mOriginator;
61
62 private int mBitmapOffsetX;
63 private int mBitmapOffsetY;
64
65 /**
66 * X offset from where we touched on the cell to its upper-left corner
67 */
68 private float mTouchOffsetX;
69
70 /**
71 * Y offset from where we touched on the cell to its upper-left corner
72 */
73 private float mTouchOffsetY;
74
75 /**
76 * Utility rectangle
77 */
78 private Rect mDragRect = new Rect();
79
80 /**
81 * Where the drag originated
82 */
83 private DragSource mDragSource;
84
85 /**
86 * The data associated with the object being dragged
87 */
88 private Object mDragInfo;
89
90 private final Rect mRect = new Rect();
91 private final int[] mDropCoordinates = new int[2];
92
93 private final Vibrator mVibrator = new Vibrator();
94
95 private DragListener mListener;
96
97 private DragScroller mDragScroller;
98
99 private static final int SCROLL_OUTSIDE_ZONE = 0;
100 private static final int SCROLL_WAITING_IN_ZONE = 1;
101
102 private static final int SCROLL_LEFT = 0;
103 private static final int SCROLL_RIGHT = 1;
104
105 private int mScrollState = SCROLL_OUTSIDE_ZONE;
106
107 private ScrollRunnable mScrollRunnable = new ScrollRunnable();
108 private View mIgnoredDropTarget;
109
110 private RectF mDragRegion;
111 private boolean mEnteredRegion;
112 private DropTarget mLastDropTarget;
113
114 private final Paint mTrashPaint = new Paint();
115 private Paint mDragPaint;
116
117 private static final int ANIMATION_STATE_STARTING = 1;
118 private static final int ANIMATION_STATE_RUNNING = 2;
119 private static final int ANIMATION_STATE_DONE = 3;
120
121 private static final int ANIMATION_TYPE_SCALE = 1;
122
123 private float mAnimationFrom;
124 private float mAnimationTo;
125 private int mAnimationDuration;
126 private long mAnimationStartTime;
127 private int mAnimationType;
128 private int mAnimationState = ANIMATION_STATE_DONE;
129
130 /**
131 * Used to create a new DragLayer from XML.
132 *
133 * @param context The application's context.
134 * @param attrs The attribtues set containing the Workspace's customization values.
135 */
136 public DragLayer(Context context, AttributeSet attrs) {
137 super(context, attrs);
138
139 final int srcColor = context.getResources().getColor(R.color.delete_color_filter);
140 mTrashPaint.setColorFilter(new PorterDuffColorFilter(srcColor, PorterDuff.Mode.SRC_ATOP));
141 }
142
143 public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
144 if (PROFILE_DRAWING_DURING_DRAG) {
145 android.os.Debug.startMethodTracing("Launcher");
146 }
The Android Open Source Project38a75b22009-03-03 14:04:32 -0800147
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700148 if (mListener != null) {
149 mListener.onDragStart(v, source, dragInfo, dragAction);
150 }
151
152 Rect r = mDragRect;
153 r.set(v.getScrollX(), v.getScrollY(), 0, 0);
154
155 offsetDescendantRectToMyCoords(v, r);
156 mTouchOffsetX = mLastMotionX - r.left;
157 mTouchOffsetY = mLastMotionY - r.top;
158
159 v.clearFocus();
160 v.setPressed(false);
161
162 boolean willNotCache = v.willNotCacheDrawing();
163 v.setWillNotCacheDrawing(false);
164 v.buildDrawingCache();
165
166 Bitmap viewBitmap = v.getDrawingCache();
167 int width = viewBitmap.getWidth();
168 int height = viewBitmap.getHeight();
169
170 Matrix scale = new Matrix();
171 float scaleFactor = v.getWidth();
172 scaleFactor = (scaleFactor + DRAG_SCALE) /scaleFactor;
173 scale.setScale(scaleFactor, scaleFactor);
174
175 mAnimationTo = 1.0f;
176 mAnimationFrom = 1.0f / scaleFactor;
177 mAnimationDuration = ANIMATION_SCALE_UP_DURATION;
178 mAnimationState = ANIMATION_STATE_STARTING;
179 mAnimationType = ANIMATION_TYPE_SCALE;
180
181 mDragBitmap = Bitmap.createBitmap(viewBitmap, 0, 0, width, height, scale, true);
182 v.destroyDrawingCache();
183 v.setWillNotCacheDrawing(willNotCache);
184
185 final Bitmap dragBitmap = mDragBitmap;
186 mBitmapOffsetX = (dragBitmap.getWidth() - width) / 2;
187 mBitmapOffsetY = (dragBitmap.getHeight() - height) / 2;
188
189 if (dragAction == DRAG_ACTION_MOVE) {
190 v.setVisibility(GONE);
191 }
192
193 mDragPaint = null;
194 mDragging = true;
195 mShouldDrop = true;
196 mOriginator = v;
197 mDragSource = source;
198 mDragInfo = dragInfo;
199
200 mVibrator.vibrate(VIBRATE_DURATION);
201
202 mEnteredRegion = false;
203
204 invalidate();
205 }
206
207 @Override
208 public boolean dispatchKeyEvent(KeyEvent event) {
209 return mDragging || super.dispatchKeyEvent(event);
210 }
211
212 @Override
213 protected void dispatchDraw(Canvas canvas) {
214 super.dispatchDraw(canvas);
215
216 if (mDragging && mDragBitmap != null) {
217 if (mAnimationState == ANIMATION_STATE_STARTING) {
218 mAnimationStartTime = SystemClock.uptimeMillis();
219 mAnimationState = ANIMATION_STATE_RUNNING;
220 }
221
222 if (mAnimationState == ANIMATION_STATE_RUNNING) {
223 float normalized = (float) (SystemClock.uptimeMillis() - mAnimationStartTime) /
224 mAnimationDuration;
225 if (normalized >= 1.0f) {
226 mAnimationState = ANIMATION_STATE_DONE;
227 }
228 normalized = Math.min(normalized, 1.0f);
229 final float value = mAnimationFrom + (mAnimationTo - mAnimationFrom) * normalized;
230
231 switch (mAnimationType) {
232 case ANIMATION_TYPE_SCALE:
233 final Bitmap dragBitmap = mDragBitmap;
234 canvas.save();
235 canvas.translate(mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
236 mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY);
237 canvas.translate((dragBitmap.getWidth() * (1.0f - value)) / 2,
238 (dragBitmap.getHeight() * (1.0f - value)) / 2);
239 canvas.scale(value, value);
240 canvas.drawBitmap(dragBitmap, 0.0f, 0.0f, mDragPaint);
241 canvas.restore();
242 break;
243 }
244 } else {
245 canvas.drawBitmap(mDragBitmap,
246 mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
247 mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY, mDragPaint);
248 }
249 }
250 }
251
252 private void endDrag() {
253 if (mDragging) {
254 mDragging = false;
255 if (mDragBitmap != null) {
256 mDragBitmap.recycle();
257 }
258 if (mOriginator != null) {
259 mOriginator.setVisibility(VISIBLE);
260 }
261 if (mListener != null) {
262 mListener.onDragEnd();
263 }
264 }
265 }
266
267 @Override
268 public boolean onInterceptTouchEvent(MotionEvent ev) {
269 final int action = ev.getAction();
270
271 final float x = ev.getX();
272 final float y = ev.getY();
273
274 switch (action) {
275 case MotionEvent.ACTION_MOVE:
276 break;
277
278 case MotionEvent.ACTION_DOWN:
279 // Remember location of down touch
280 mLastMotionX = x;
281 mLastMotionY = y;
282 mLastDropTarget = null;
283 break;
284
285 case MotionEvent.ACTION_CANCEL:
286 case MotionEvent.ACTION_UP:
287 if (mShouldDrop && drop(x, y)) {
288 mShouldDrop = false;
289 }
290 endDrag();
291 break;
292 }
293
294 return mDragging;
295 }
296
297 @Override
298 public boolean onTouchEvent(MotionEvent ev) {
299 if (!mDragging) {
300 return false;
301 }
302
303 final int action = ev.getAction();
304 final float x = ev.getX();
305 final float y = ev.getY();
306
307 switch (action) {
308 case MotionEvent.ACTION_DOWN:
309
310 // Remember where the motion event started
311 mLastMotionX = x;
312 mLastMotionY = y;
313
314 if ((x < SCROLL_ZONE) || (x > getWidth() - SCROLL_ZONE)) {
315 mScrollState = SCROLL_WAITING_IN_ZONE;
316 postDelayed(mScrollRunnable, SCROLL_DELAY);
317 } else {
318 mScrollState = SCROLL_OUTSIDE_ZONE;
319 }
320
321 break;
322 case MotionEvent.ACTION_MOVE:
The Android Open Source Project15a88802009-02-10 15:44:05 -0800323 final int scrollX = mScrollX;
324 final int scrollY = mScrollY;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700325
The Android Open Source Project15a88802009-02-10 15:44:05 -0800326 final float touchX = mTouchOffsetX;
327 final float touchY = mTouchOffsetY;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700328
The Android Open Source Project15a88802009-02-10 15:44:05 -0800329 final int offsetX = mBitmapOffsetX;
330 final int offsetY = mBitmapOffsetY;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700331
The Android Open Source Project15a88802009-02-10 15:44:05 -0800332 int left = (int) (scrollX + mLastMotionX - touchX - offsetX);
333 int top = (int) (scrollY + mLastMotionY - touchY - offsetY);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700334
The Android Open Source Project15a88802009-02-10 15:44:05 -0800335 final Bitmap dragBitmap = mDragBitmap;
336 final int width = dragBitmap.getWidth();
337 final int height = dragBitmap.getHeight();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700338
The Android Open Source Project15a88802009-02-10 15:44:05 -0800339 final Rect rect = mRect;
340 rect.set(left - 1, top - 1, left + width + 1, top + height + 1);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700341
The Android Open Source Project15a88802009-02-10 15:44:05 -0800342 mLastMotionX = x;
343 mLastMotionY = y;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700344
The Android Open Source Project15a88802009-02-10 15:44:05 -0800345 left = (int) (scrollX + x - touchX - offsetX);
346 top = (int) (scrollY + y - touchY - offsetY);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700347
The Android Open Source Project15a88802009-02-10 15:44:05 -0800348 rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
349 invalidate(rect);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700350
351 final int[] coordinates = mDropCoordinates;
352 DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
353 if (dropTarget != null) {
354 if (mLastDropTarget == dropTarget) {
355 dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
356 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
357 } else {
358 if (mLastDropTarget != null) {
359 mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
360 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
361 }
362 dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
363 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
364 }
365 } else {
366 if (mLastDropTarget != null) {
367 mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
368 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
369 }
370 }
371 mLastDropTarget = dropTarget;
372
373 boolean inDragRegion = false;
374 if (mDragRegion != null) {
375 final RectF region = mDragRegion;
376 final boolean inRegion = region.contains(ev.getRawX(), ev.getRawY());
377 if (!mEnteredRegion && inRegion) {
378 mDragPaint = mTrashPaint;
379 mEnteredRegion = true;
380 inDragRegion = true;
381 } else if (mEnteredRegion && !inRegion) {
382 mDragPaint = null;
383 mEnteredRegion = false;
384 }
385 }
386
387 if (!inDragRegion && x < SCROLL_ZONE) {
388 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
389 mScrollState = SCROLL_WAITING_IN_ZONE;
390 mScrollRunnable.setDirection(SCROLL_LEFT);
391 postDelayed(mScrollRunnable, SCROLL_DELAY);
392 }
393 } else if (!inDragRegion && x > getWidth() - SCROLL_ZONE) {
394 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
395 mScrollState = SCROLL_WAITING_IN_ZONE;
396 mScrollRunnable.setDirection(SCROLL_RIGHT);
397 postDelayed(mScrollRunnable, SCROLL_DELAY);
398 }
399 } else {
400 if (mScrollState == SCROLL_WAITING_IN_ZONE) {
401 mScrollState = SCROLL_OUTSIDE_ZONE;
402 mScrollRunnable.setDirection(SCROLL_RIGHT);
403 removeCallbacks(mScrollRunnable);
404 }
405 }
406
407 break;
408 case MotionEvent.ACTION_UP:
409 removeCallbacks(mScrollRunnable);
410 if (mShouldDrop) {
411 drop(x, y);
412 mShouldDrop = false;
413 }
414 endDrag();
415
416 break;
417 case MotionEvent.ACTION_CANCEL:
418 endDrag();
419 }
420
421 return true;
422 }
423
424 private boolean drop(float x, float y) {
425 invalidate();
426
427 final int[] coordinates = mDropCoordinates;
428 DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
429
430 if (dropTarget != null) {
431 dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
432 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
433 if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
434 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo)) {
435 dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
436 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
437 mDragSource.onDropCompleted((View) dropTarget, true);
438 return true;
439 } else {
440 mDragSource.onDropCompleted((View) dropTarget, false);
441 return true;
442 }
443 }
444 return false;
445 }
446
447 DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
448 return findDropTarget(this, x, y, dropCoordinates);
449 }
450
451 private DropTarget findDropTarget(ViewGroup container, int x, int y, int[] dropCoordinates) {
452 final Rect r = mDragRect;
453 final int count = container.getChildCount();
454 final int scrolledX = x + container.getScrollX();
455 final int scrolledY = y + container.getScrollY();
456 final View ignoredDropTarget = mIgnoredDropTarget;
457
458 for (int i = count - 1; i >= 0; i--) {
459 final View child = container.getChildAt(i);
460 if (child.getVisibility() == VISIBLE && child != ignoredDropTarget) {
461 child.getHitRect(r);
462 if (r.contains(scrolledX, scrolledY)) {
463 DropTarget target = null;
464 if (child instanceof ViewGroup) {
465 x = scrolledX - child.getLeft();
466 y = scrolledY - child.getTop();
467 target = findDropTarget((ViewGroup) child, x, y, dropCoordinates);
468 }
469 if (target == null) {
470 if (child instanceof DropTarget) {
471 dropCoordinates[0] = x;
472 dropCoordinates[1] = y;
473 return (DropTarget) child;
474 }
475 } else {
476 return target;
477 }
478 }
479 }
480 }
481
482 return null;
483 }
484
485 public void setDragScoller(DragScroller scroller) {
486 mDragScroller = scroller;
487 }
488
489 public void setDragListener(DragListener l) {
490 mListener = l;
491 }
492
493 public void removeDragListener(DragListener l) {
494 mListener = null;
495 }
496
497 /**
498 * Specifies the view that must be ignored when looking for a drop target.
499 *
500 * @param view The view that will not be taken into account while looking
501 * for a drop target.
502 */
503 void setIgnoredDropTarget(View view) {
504 mIgnoredDropTarget = view;
505 }
506
507 /**
508 * Specifies the delete region.
509 *
510 * @param region The rectangle in screen coordinates of the delete region.
511 */
512 void setDeleteRegion(RectF region) {
513 mDragRegion = region;
514 }
515
516 private class ScrollRunnable implements Runnable {
517 private int mDirection;
518
519 ScrollRunnable() {
520 }
521
522 public void run() {
523 if (mDragScroller != null) {
524 if (mDirection == SCROLL_LEFT) {
525 mDragScroller.scrollLeft();
526 } else {
527 mDragScroller.scrollRight();
528 }
529 mScrollState = SCROLL_OUTSIDE_ZONE;
530 }
531 }
532
533 void setDirection(int direction) {
534 mDirection = direction;
535 }
536 }
537}