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