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