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