• 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.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.graphics.Canvas;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewParent;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.view.accessibility.AccessibilityManager;
37 import android.view.animation.DecelerateInterpolator;
38 import android.view.animation.Interpolator;
39 import android.widget.FrameLayout;
40 import android.widget.TextView;
41 
42 import com.android.launcher.R;
43 
44 import java.util.ArrayList;
45 
46 /**
47  * A ViewGroup that coordinates dragging across its descendants
48  */
49 public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener {
50     private DragController mDragController;
51     private int[] mTmpXY = new int[2];
52 
53     private int mXDown, mYDown;
54     private Launcher mLauncher;
55 
56     // Variables relating to resizing widgets
57     private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
58             new ArrayList<AppWidgetResizeFrame>();
59     private AppWidgetResizeFrame mCurrentResizeFrame;
60 
61     // Variables relating to animation of views after drop
62     private ValueAnimator mDropAnim = null;
63     private ValueAnimator mFadeOutAnim = null;
64     private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
65     private DragView mDropView = null;
66     private int mAnchorViewInitialScrollX = 0;
67     private View mAnchorView = null;
68 
69     private boolean mHoverPointClosesFolder = false;
70     private Rect mHitRect = new Rect();
71     private int mWorkspaceIndex = -1;
72     private int mQsbIndex = -1;
73     public static final int ANIMATION_END_DISAPPEAR = 0;
74     public static final int ANIMATION_END_FADE_OUT = 1;
75     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
76 
77     /**
78      * Used to create a new DragLayer from XML.
79      *
80      * @param context The application's context.
81      * @param attrs The attributes set containing the Workspace's customization values.
82      */
DragLayer(Context context, AttributeSet attrs)83     public DragLayer(Context context, AttributeSet attrs) {
84         super(context, attrs);
85 
86         // Disable multitouch across the workspace/all apps/customize tray
87         setMotionEventSplittingEnabled(false);
88         setChildrenDrawingOrderEnabled(true);
89         setOnHierarchyChangeListener(this);
90 
91         mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
92         mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
93     }
94 
setup(Launcher launcher, DragController controller)95     public void setup(Launcher launcher, DragController controller) {
96         mLauncher = launcher;
97         mDragController = controller;
98     }
99 
100     @Override
dispatchKeyEvent(KeyEvent event)101     public boolean dispatchKeyEvent(KeyEvent event) {
102         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
103     }
104 
isEventOverFolderTextRegion(Folder folder, MotionEvent ev)105     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
106         getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
107         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
108             return true;
109         }
110         return false;
111     }
112 
isEventOverFolder(Folder folder, MotionEvent ev)113     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
114         getDescendantRectRelativeToSelf(folder, mHitRect);
115         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
116             return true;
117         }
118         return false;
119     }
120 
handleTouchDown(MotionEvent ev, boolean intercept)121     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
122         Rect hitRect = new Rect();
123         int x = (int) ev.getX();
124         int y = (int) ev.getY();
125 
126         for (AppWidgetResizeFrame child: mResizeFrames) {
127             child.getHitRect(hitRect);
128             if (hitRect.contains(x, y)) {
129                 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
130                     mCurrentResizeFrame = child;
131                     mXDown = x;
132                     mYDown = y;
133                     requestDisallowInterceptTouchEvent(true);
134                     return true;
135                 }
136             }
137         }
138 
139         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
140         if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
141             if (currentFolder.isEditingName()) {
142                 if (!isEventOverFolderTextRegion(currentFolder, ev)) {
143                     currentFolder.dismissEditingName();
144                     return true;
145                 }
146             }
147 
148             getDescendantRectRelativeToSelf(currentFolder, hitRect);
149             if (!isEventOverFolder(currentFolder, ev)) {
150                 mLauncher.closeFolder();
151                 return true;
152             }
153         }
154         return false;
155     }
156 
157     @Override
onInterceptTouchEvent(MotionEvent ev)158     public boolean onInterceptTouchEvent(MotionEvent ev) {
159         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
160             if (handleTouchDown(ev, true)) {
161                 return true;
162             }
163         }
164         clearAllResizeFrames();
165         return mDragController.onInterceptTouchEvent(ev);
166     }
167 
168     @Override
onInterceptHoverEvent(MotionEvent ev)169     public boolean onInterceptHoverEvent(MotionEvent ev) {
170         if (mLauncher == null || mLauncher.getWorkspace() == null) {
171             return false;
172         }
173         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
174         if (currentFolder == null) {
175             return false;
176         } else {
177                 AccessibilityManager accessibilityManager = (AccessibilityManager)
178                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
179             if (accessibilityManager.isTouchExplorationEnabled()) {
180                 final int action = ev.getAction();
181                 boolean isOverFolder;
182                 switch (action) {
183                     case MotionEvent.ACTION_HOVER_ENTER:
184                         isOverFolder = isEventOverFolder(currentFolder, ev);
185                         if (!isOverFolder) {
186                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
187                             mHoverPointClosesFolder = true;
188                             return true;
189                         } else if (isOverFolder) {
190                             mHoverPointClosesFolder = false;
191                         } else {
192                             return true;
193                         }
194                     case MotionEvent.ACTION_HOVER_MOVE:
195                         isOverFolder = isEventOverFolder(currentFolder, ev);
196                         if (!isOverFolder && !mHoverPointClosesFolder) {
197                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
198                             mHoverPointClosesFolder = true;
199                             return true;
200                         } else if (isOverFolder) {
201                             mHoverPointClosesFolder = false;
202                         } else {
203                             return true;
204                         }
205                 }
206             }
207         }
208         return false;
209     }
210 
sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)211     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
212         AccessibilityManager accessibilityManager = (AccessibilityManager)
213                 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
214         if (accessibilityManager.isEnabled()) {
215             int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
216             AccessibilityEvent event = AccessibilityEvent.obtain(
217                     AccessibilityEvent.TYPE_VIEW_FOCUSED);
218             onInitializeAccessibilityEvent(event);
219             event.getText().add(getContext().getString(stringId));
220             accessibilityManager.sendAccessibilityEvent(event);
221         }
222     }
223 
224     @Override
onHoverEvent(MotionEvent ev)225     public boolean onHoverEvent(MotionEvent ev) {
226         // If we've received this, we've already done the necessary handling
227         // in onInterceptHoverEvent. Return true to consume the event.
228         return false;
229     }
230 
231     @Override
onTouchEvent(MotionEvent ev)232     public boolean onTouchEvent(MotionEvent ev) {
233         boolean handled = false;
234         int action = ev.getAction();
235 
236         int x = (int) ev.getX();
237         int y = (int) ev.getY();
238 
239         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
240             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
241                 if (handleTouchDown(ev, false)) {
242                     return true;
243                 }
244             }
245         }
246 
247         if (mCurrentResizeFrame != null) {
248             handled = true;
249             switch (action) {
250                 case MotionEvent.ACTION_MOVE:
251                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
252                     break;
253                 case MotionEvent.ACTION_CANCEL:
254                 case MotionEvent.ACTION_UP:
255                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
256                     mCurrentResizeFrame.onTouchUp();
257                     mCurrentResizeFrame = null;
258             }
259         }
260         if (handled) return true;
261         return mDragController.onTouchEvent(ev);
262     }
263 
264     /**
265      * Determine the rect of the descendant in this DragLayer's coordinates
266      *
267      * @param descendant The descendant whose coordinates we want to find.
268      * @param r The rect into which to place the results.
269      * @return The factor by which this descendant is scaled relative to this DragLayer.
270      */
getDescendantRectRelativeToSelf(View descendant, Rect r)271     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
272         mTmpXY[0] = 0;
273         mTmpXY[1] = 0;
274         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
275         r.set(mTmpXY[0], mTmpXY[1],
276                 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
277         return scale;
278     }
279 
getLocationInDragLayer(View child, int[] loc)280     public float getLocationInDragLayer(View child, int[] loc) {
281         loc[0] = 0;
282         loc[1] = 0;
283         return getDescendantCoordRelativeToSelf(child, loc);
284     }
285 
286     /**
287      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
288      * coordinates.
289      *
290      * @param descendant The descendant to which the passed coordinate is relative.
291      * @param coord The coordinate that we want mapped.
292      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
293      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
294      *         assumption fails, we will need to return a pair of scale factors.
295      */
getDescendantCoordRelativeToSelf(View descendant, int[] coord)296     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
297         float scale = 1.0f;
298         float[] pt = {coord[0], coord[1]};
299         descendant.getMatrix().mapPoints(pt);
300         scale *= descendant.getScaleX();
301         pt[0] += descendant.getLeft();
302         pt[1] += descendant.getTop();
303         ViewParent viewParent = descendant.getParent();
304         while (viewParent instanceof View && viewParent != this) {
305             final View view = (View)viewParent;
306             view.getMatrix().mapPoints(pt);
307             scale *= view.getScaleX();
308             pt[0] += view.getLeft() - view.getScrollX();
309             pt[1] += view.getTop() - view.getScrollY();
310             viewParent = view.getParent();
311         }
312         coord[0] = (int) Math.round(pt[0]);
313         coord[1] = (int) Math.round(pt[1]);
314         return scale;
315     }
316 
getViewRectRelativeToSelf(View v, Rect r)317     public void getViewRectRelativeToSelf(View v, Rect r) {
318         int[] loc = new int[2];
319         getLocationInWindow(loc);
320         int x = loc[0];
321         int y = loc[1];
322 
323         v.getLocationInWindow(loc);
324         int vX = loc[0];
325         int vY = loc[1];
326 
327         int left = vX - x;
328         int top = vY - y;
329         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
330     }
331 
332     @Override
dispatchUnhandledMove(View focused, int direction)333     public boolean dispatchUnhandledMove(View focused, int direction) {
334         return mDragController.dispatchUnhandledMove(focused, direction);
335     }
336 
337     public static class LayoutParams extends FrameLayout.LayoutParams {
338         public int x, y;
339         public boolean customPosition = false;
340 
341         /**
342          * {@inheritDoc}
343          */
LayoutParams(int width, int height)344         public LayoutParams(int width, int height) {
345             super(width, height);
346         }
347 
setWidth(int width)348         public void setWidth(int width) {
349             this.width = width;
350         }
351 
getWidth()352         public int getWidth() {
353             return width;
354         }
355 
setHeight(int height)356         public void setHeight(int height) {
357             this.height = height;
358         }
359 
getHeight()360         public int getHeight() {
361             return height;
362         }
363 
setX(int x)364         public void setX(int x) {
365             this.x = x;
366         }
367 
getX()368         public int getX() {
369             return x;
370         }
371 
setY(int y)372         public void setY(int y) {
373             this.y = y;
374         }
375 
getY()376         public int getY() {
377             return y;
378         }
379     }
380 
onLayout(boolean changed, int l, int t, int r, int b)381     protected void onLayout(boolean changed, int l, int t, int r, int b) {
382         super.onLayout(changed, l, t, r, b);
383         int count = getChildCount();
384         for (int i = 0; i < count; i++) {
385             View child = getChildAt(i);
386             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
387             if (flp instanceof LayoutParams) {
388                 final LayoutParams lp = (LayoutParams) flp;
389                 if (lp.customPosition) {
390                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
391                 }
392             }
393         }
394     }
395 
clearAllResizeFrames()396     public void clearAllResizeFrames() {
397         if (mResizeFrames.size() > 0) {
398             for (AppWidgetResizeFrame frame: mResizeFrames) {
399                 frame.commitResize();
400                 removeView(frame);
401             }
402             mResizeFrames.clear();
403         }
404     }
405 
hasResizeFrames()406     public boolean hasResizeFrames() {
407         return mResizeFrames.size() > 0;
408     }
409 
isWidgetBeingResized()410     public boolean isWidgetBeingResized() {
411         return mCurrentResizeFrame != null;
412     }
413 
addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout)414     public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
415             CellLayout cellLayout) {
416         AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
417                 widget, cellLayout, this);
418 
419         LayoutParams lp = new LayoutParams(-1, -1);
420         lp.customPosition = true;
421 
422         addView(resizeFrame, lp);
423         mResizeFrames.add(resizeFrame);
424 
425         resizeFrame.snapToWidget(false);
426     }
427 
animateViewIntoPosition(DragView dragView, final View child)428     public void animateViewIntoPosition(DragView dragView, final View child) {
429         animateViewIntoPosition(dragView, child, null);
430     }
431 
animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)432     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
433             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
434             int duration) {
435         Rect r = new Rect();
436         getViewRectRelativeToSelf(dragView, r);
437         final int fromX = r.left;
438         final int fromY = r.top;
439 
440         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
441                 onFinishRunnable, animationEndStyle, duration, null);
442     }
443 
animateViewIntoPosition(DragView dragView, final View child, final Runnable onFinishAnimationRunnable)444     public void animateViewIntoPosition(DragView dragView, final View child,
445             final Runnable onFinishAnimationRunnable) {
446         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
447     }
448 
animateViewIntoPosition(DragView dragView, final View child, int duration, final Runnable onFinishAnimationRunnable, View anchorView)449     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
450             final Runnable onFinishAnimationRunnable, View anchorView) {
451         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
452         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
453         parentChildren.measureChild(child);
454 
455         Rect r = new Rect();
456         getViewRectRelativeToSelf(dragView, r);
457 
458         int coord[] = new int[2];
459         float childScale = child.getScaleX();
460         coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
461         coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
462 
463         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
464         // the correct coordinates (above) and use these to determine the final location
465         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
466         // We need to account for the scale of the child itself, as the above only accounts for
467         // for the scale in parents.
468         scale *= childScale;
469         int toX = coord[0];
470         int toY = coord[1];
471         if (child instanceof TextView) {
472             TextView tv = (TextView) child;
473 
474             // The child may be scaled (always about the center of the view) so to account for it,
475             // we have to offset the position by the scaled size.  Once we do that, we can center
476             // the drag view about the scaled child view.
477             toY += Math.round(scale * tv.getPaddingTop());
478             toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
479             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
480         } else if (child instanceof FolderIcon) {
481             // Account for holographic blur padding on the drag view
482             toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
483             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
484             // Center in the x coordinate about the target's drawable
485             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
486         } else {
487             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
488             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
489                     - child.getMeasuredWidth()))) / 2;
490         }
491 
492         final int fromX = r.left;
493         final int fromY = r.top;
494         child.setVisibility(INVISIBLE);
495         Runnable onCompleteRunnable = new Runnable() {
496             public void run() {
497                 child.setVisibility(VISIBLE);
498                 if (onFinishAnimationRunnable != null) {
499                     onFinishAnimationRunnable.run();
500                 }
501             }
502         };
503         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
504                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
505     }
506 
animateViewIntoPosition(final DragView view, final int fromX, final int fromY, final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, int animationEndStyle, int duration, View anchorView)507     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
508             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
509             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
510             int animationEndStyle, int duration, View anchorView) {
511         Rect from = new Rect(fromX, fromY, fromX +
512                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
513         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
514         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
515                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
516     }
517 
518     /**
519      * This method animates a view at the end of a drag and drop animation.
520      *
521      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
522      *        doesn't need to be a child of DragLayer.
523      * @param from The initial location of the view. Only the left and top parameters are used.
524      * @param to The final location of the view. Only the left and top parameters are used. This
525      *        location doesn't account for scaling, and so should be centered about the desired
526      *        final location (including scaling).
527      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
528      * @param finalScale The final scale of the view. The view is scaled about its center.
529      * @param duration The duration of the animation.
530      * @param motionInterpolator The interpolator to use for the location of the view.
531      * @param alphaInterpolator The interpolator to use for the alpha of the view.
532      * @param onCompleteRunnable Optional runnable to run on animation completion.
533      * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
534      *        the runnable will execute after the view is faded out.
535      * @param anchorView If not null, this represents the view which the animated view stays
536      *        anchored to in case scrolling is currently taking place. Note: currently this is
537      *        only used for the X dimension for the case of the workspace.
538      */
animateView(final DragView view, final Rect from, final Rect to, final float finalAlpha, final float initScaleX, final float initScaleY, final float finalScaleX, final float finalScaleY, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)539     public void animateView(final DragView view, final Rect from, final Rect to,
540             final float finalAlpha, final float initScaleX, final float initScaleY,
541             final float finalScaleX, final float finalScaleY, int duration,
542             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
543             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
544 
545         // Calculate the duration of the animation based on the object's distance
546         final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
547                 Math.pow(to.top - from.top, 2));
548         final Resources res = getResources();
549         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
550 
551         // If duration < 0, this is a cue to compute the duration based on the distance
552         if (duration < 0) {
553             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
554             if (dist < maxDist) {
555                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
556             }
557             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
558         }
559 
560         // Fall back to cubic ease out interpolator for the animation if none is specified
561         TimeInterpolator interpolator = null;
562         if (alphaInterpolator == null || motionInterpolator == null) {
563             interpolator = mCubicEaseOutInterpolator;
564         }
565 
566         // Animate the view
567         final float initAlpha = view.getAlpha();
568         final float dropViewScale = view.getScaleX();
569         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
570             @Override
571             public void onAnimationUpdate(ValueAnimator animation) {
572                 final float percent = (Float) animation.getAnimatedValue();
573                 final int width = view.getMeasuredWidth();
574                 final int height = view.getMeasuredHeight();
575 
576                 float alphaPercent = alphaInterpolator == null ? percent :
577                         alphaInterpolator.getInterpolation(percent);
578                 float motionPercent = motionInterpolator == null ? percent :
579                         motionInterpolator.getInterpolation(percent);
580 
581                 float initialScaleX = initScaleX * dropViewScale;
582                 float initialScaleY = initScaleY * dropViewScale;
583                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
584                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
585                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
586 
587                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
588                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
589 
590                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
591                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
592 
593                 int xPos = x - mDropView.getScrollX() + (mAnchorView != null
594                         ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
595                 int yPos = y - mDropView.getScrollY();
596 
597                 mDropView.setTranslationX(xPos);
598                 mDropView.setTranslationY(yPos);
599                 mDropView.setScaleX(scaleX);
600                 mDropView.setScaleY(scaleY);
601                 mDropView.setAlpha(alpha);
602             }
603         };
604         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
605                 anchorView);
606     }
607 
animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)608     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
609             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
610             final int animationEndStyle, View anchorView) {
611         // Clean up the previous animations
612         if (mDropAnim != null) mDropAnim.cancel();
613         if (mFadeOutAnim != null) mFadeOutAnim.cancel();
614 
615         // Show the drop view if it was previously hidden
616         mDropView = view;
617         mDropView.cancelAnimation();
618         mDropView.resetLayoutParams();
619 
620         // Set the anchor view if the page is scrolling
621         if (anchorView != null) {
622             mAnchorViewInitialScrollX = anchorView.getScrollX();
623         }
624         mAnchorView = anchorView;
625 
626         // Create and start the animation
627         mDropAnim = new ValueAnimator();
628         mDropAnim.setInterpolator(interpolator);
629         mDropAnim.setDuration(duration);
630         mDropAnim.setFloatValues(0f, 1f);
631         mDropAnim.addUpdateListener(updateCb);
632         mDropAnim.addListener(new AnimatorListenerAdapter() {
633             public void onAnimationEnd(Animator animation) {
634                 if (onCompleteRunnable != null) {
635                     onCompleteRunnable.run();
636                 }
637                 switch (animationEndStyle) {
638                 case ANIMATION_END_DISAPPEAR:
639                     clearAnimatedView();
640                     break;
641                 case ANIMATION_END_FADE_OUT:
642                     fadeOutDragView();
643                     break;
644                 case ANIMATION_END_REMAIN_VISIBLE:
645                     break;
646                 }
647             }
648         });
649         mDropAnim.start();
650     }
651 
clearAnimatedView()652     public void clearAnimatedView() {
653         if (mDropAnim != null) {
654             mDropAnim.cancel();
655         }
656         if (mDropView != null) {
657             mDragController.onDeferredEndDrag(mDropView);
658         }
659         mDropView = null;
660         invalidate();
661     }
662 
getAnimatedView()663     public View getAnimatedView() {
664         return mDropView;
665     }
666 
fadeOutDragView()667     private void fadeOutDragView() {
668         mFadeOutAnim = new ValueAnimator();
669         mFadeOutAnim.setDuration(150);
670         mFadeOutAnim.setFloatValues(0f, 1f);
671         mFadeOutAnim.removeAllUpdateListeners();
672         mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
673             public void onAnimationUpdate(ValueAnimator animation) {
674                 final float percent = (Float) animation.getAnimatedValue();
675 
676                 float alpha = 1 - percent;
677                 mDropView.setAlpha(alpha);
678             }
679         });
680         mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
681             public void onAnimationEnd(Animator animation) {
682                 if (mDropView != null) {
683                     mDragController.onDeferredEndDrag(mDropView);
684                 }
685                 mDropView = null;
686                 invalidate();
687             }
688         });
689         mFadeOutAnim.start();
690     }
691 
692     @Override
onChildViewAdded(View parent, View child)693     public void onChildViewAdded(View parent, View child) {
694         updateChildIndices();
695     }
696 
697     @Override
onChildViewRemoved(View parent, View child)698     public void onChildViewRemoved(View parent, View child) {
699         updateChildIndices();
700     }
701 
updateChildIndices()702     private void updateChildIndices() {
703         if (mLauncher != null) {
704             mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
705             mQsbIndex = indexOfChild(mLauncher.getSearchBar());
706         }
707     }
708 
709     @Override
getChildDrawingOrder(int childCount, int i)710     protected int getChildDrawingOrder(int childCount, int i) {
711         // TODO: We have turned off this custom drawing order because it now effects touch
712         // dispatch order. We need to sort that issue out and then decide how to go about this.
713         if (true || LauncherApplication.isScreenLandscape(getContext()) ||
714                 mWorkspaceIndex == -1 || mQsbIndex == -1 ||
715                 mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
716             return i;
717         }
718 
719         // This ensures that the workspace is drawn above the hotseat and qsb,
720         // except when the workspace is drawing a background gradient, in which
721         // case we want the workspace to stay behind these elements.
722         if (i == mQsbIndex) {
723             return mWorkspaceIndex;
724         } else if (i == mWorkspaceIndex) {
725             return mQsbIndex;
726         } else {
727             return i;
728         }
729     }
730 
731     private boolean mInScrollArea;
732     private Drawable mLeftHoverDrawable;
733     private Drawable mRightHoverDrawable;
734 
onEnterScrollArea(int direction)735     void onEnterScrollArea(int direction) {
736         mInScrollArea = true;
737         invalidate();
738     }
739 
onExitScrollArea()740     void onExitScrollArea() {
741         mInScrollArea = false;
742         invalidate();
743     }
744 
745     @Override
dispatchDraw(Canvas canvas)746     protected void dispatchDraw(Canvas canvas) {
747         super.dispatchDraw(canvas);
748 
749         if (mInScrollArea && !LauncherApplication.isScreenLarge()) {
750             Workspace workspace = mLauncher.getWorkspace();
751             int width = workspace.getWidth();
752             Rect childRect = new Rect();
753             getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
754 
755             int page = workspace.getNextPage();
756             CellLayout leftPage = (CellLayout) workspace.getChildAt(page - 1);
757             CellLayout rightPage = (CellLayout) workspace.getChildAt(page + 1);
758 
759             if (leftPage != null && leftPage.getIsDragOverlapping()) {
760                 mLeftHoverDrawable.setBounds(0, childRect.top,
761                         mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
762                 mLeftHoverDrawable.draw(canvas);
763             } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
764                 mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
765                         childRect.top, width, childRect.bottom);
766                 mRightHoverDrawable.draw(canvas);
767             }
768         }
769     }
770 }
771