blob: 56140dd07669877cf20860b3fcd06636cef8e85f [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 }
147
148 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:
323 if (Launcher.sOpenGlEnabled) {
324 mLastMotionX = x;
325 mLastMotionY = y;
326
327 invalidate();
328 } else {
329 final int scrollX = mScrollX;
330 final int scrollY = mScrollY;
331
332 final float touchX = mTouchOffsetX;
333 final float touchY = mTouchOffsetY;
334
335 final int offsetX = mBitmapOffsetX;
336 final int offsetY = mBitmapOffsetY;
337
338 int left = (int) (scrollX + mLastMotionX - touchX - offsetX);
339 int top = (int) (scrollY + mLastMotionY - touchY - offsetY);
340
341 final Bitmap dragBitmap = mDragBitmap;
342 final int width = dragBitmap.getWidth();
343 final int height = dragBitmap.getHeight();
344
345 final Rect rect = mRect;
346 rect.set(left - 1, top - 1, left + width + 1, top + height + 1);
347
348 mLastMotionX = x;
349 mLastMotionY = y;
350
351 left = (int) (scrollX + x - touchX - offsetX);
352 top = (int) (scrollY + y - touchY - offsetY);
353
354 rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
355 invalidate(rect);
356 }
357
358 final int[] coordinates = mDropCoordinates;
359 DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
360 if (dropTarget != null) {
361 if (mLastDropTarget == dropTarget) {
362 dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
363 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
364 } else {
365 if (mLastDropTarget != null) {
366 mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
367 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
368 }
369 dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
370 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
371 }
372 } else {
373 if (mLastDropTarget != null) {
374 mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
375 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
376 }
377 }
378 mLastDropTarget = dropTarget;
379
380 boolean inDragRegion = false;
381 if (mDragRegion != null) {
382 final RectF region = mDragRegion;
383 final boolean inRegion = region.contains(ev.getRawX(), ev.getRawY());
384 if (!mEnteredRegion && inRegion) {
385 mDragPaint = mTrashPaint;
386 mEnteredRegion = true;
387 inDragRegion = true;
388 } else if (mEnteredRegion && !inRegion) {
389 mDragPaint = null;
390 mEnteredRegion = false;
391 }
392 }
393
394 if (!inDragRegion && x < SCROLL_ZONE) {
395 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
396 mScrollState = SCROLL_WAITING_IN_ZONE;
397 mScrollRunnable.setDirection(SCROLL_LEFT);
398 postDelayed(mScrollRunnable, SCROLL_DELAY);
399 }
400 } else if (!inDragRegion && x > getWidth() - SCROLL_ZONE) {
401 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
402 mScrollState = SCROLL_WAITING_IN_ZONE;
403 mScrollRunnable.setDirection(SCROLL_RIGHT);
404 postDelayed(mScrollRunnable, SCROLL_DELAY);
405 }
406 } else {
407 if (mScrollState == SCROLL_WAITING_IN_ZONE) {
408 mScrollState = SCROLL_OUTSIDE_ZONE;
409 mScrollRunnable.setDirection(SCROLL_RIGHT);
410 removeCallbacks(mScrollRunnable);
411 }
412 }
413
414 break;
415 case MotionEvent.ACTION_UP:
416 removeCallbacks(mScrollRunnable);
417 if (mShouldDrop) {
418 drop(x, y);
419 mShouldDrop = false;
420 }
421 endDrag();
422
423 break;
424 case MotionEvent.ACTION_CANCEL:
425 endDrag();
426 }
427
428 return true;
429 }
430
431 private boolean drop(float x, float y) {
432 invalidate();
433
434 final int[] coordinates = mDropCoordinates;
435 DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
436
437 if (dropTarget != null) {
438 dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
439 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
440 if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
441 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo)) {
442 dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
443 (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
444 mDragSource.onDropCompleted((View) dropTarget, true);
445 return true;
446 } else {
447 mDragSource.onDropCompleted((View) dropTarget, false);
448 return true;
449 }
450 }
451 return false;
452 }
453
454 DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
455 return findDropTarget(this, x, y, dropCoordinates);
456 }
457
458 private DropTarget findDropTarget(ViewGroup container, int x, int y, int[] dropCoordinates) {
459 final Rect r = mDragRect;
460 final int count = container.getChildCount();
461 final int scrolledX = x + container.getScrollX();
462 final int scrolledY = y + container.getScrollY();
463 final View ignoredDropTarget = mIgnoredDropTarget;
464
465 for (int i = count - 1; i >= 0; i--) {
466 final View child = container.getChildAt(i);
467 if (child.getVisibility() == VISIBLE && child != ignoredDropTarget) {
468 child.getHitRect(r);
469 if (r.contains(scrolledX, scrolledY)) {
470 DropTarget target = null;
471 if (child instanceof ViewGroup) {
472 x = scrolledX - child.getLeft();
473 y = scrolledY - child.getTop();
474 target = findDropTarget((ViewGroup) child, x, y, dropCoordinates);
475 }
476 if (target == null) {
477 if (child instanceof DropTarget) {
478 dropCoordinates[0] = x;
479 dropCoordinates[1] = y;
480 return (DropTarget) child;
481 }
482 } else {
483 return target;
484 }
485 }
486 }
487 }
488
489 return null;
490 }
491
492 public void setDragScoller(DragScroller scroller) {
493 mDragScroller = scroller;
494 }
495
496 public void setDragListener(DragListener l) {
497 mListener = l;
498 }
499
500 public void removeDragListener(DragListener l) {
501 mListener = null;
502 }
503
504 /**
505 * Specifies the view that must be ignored when looking for a drop target.
506 *
507 * @param view The view that will not be taken into account while looking
508 * for a drop target.
509 */
510 void setIgnoredDropTarget(View view) {
511 mIgnoredDropTarget = view;
512 }
513
514 /**
515 * Specifies the delete region.
516 *
517 * @param region The rectangle in screen coordinates of the delete region.
518 */
519 void setDeleteRegion(RectF region) {
520 mDragRegion = region;
521 }
522
523 private class ScrollRunnable implements Runnable {
524 private int mDirection;
525
526 ScrollRunnable() {
527 }
528
529 public void run() {
530 if (mDragScroller != null) {
531 if (mDirection == SCROLL_LEFT) {
532 mDragScroller.scrollLeft();
533 } else {
534 mDragScroller.scrollRight();
535 }
536 mScrollState = SCROLL_OUTSIDE_ZONE;
537 }
538 }
539
540 void setDirection(int direction) {
541 mDirection = direction;
542 }
543 }
544}