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