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.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      */
     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 
     95     public void setup(Launcher launcher, DragController controller) {
     96         mLauncher = launcher;
     97         mDragController = controller;
     98     }
     99 
    100     @Override
    101     public boolean dispatchKeyEvent(KeyEvent event) {
    102         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
    103     }
    104 
    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 
    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 
    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
    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
    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 
    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
    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
    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      */
    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 
    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      */
    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 
    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
    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          */
    344         public LayoutParams(int width, int height) {
    345             super(width, height);
    346         }
    347 
    348         public void setWidth(int width) {
    349             this.width = width;
    350         }
    351 
    352         public int getWidth() {
    353             return width;
    354         }
    355 
    356         public void setHeight(int height) {
    357             this.height = height;
    358         }
    359 
    360         public int getHeight() {
    361             return height;
    362         }
    363 
    364         public void setX(int x) {
    365             this.x = x;
    366         }
    367 
    368         public int getX() {
    369             return x;
    370         }
    371 
    372         public void setY(int y) {
    373             this.y = y;
    374         }
    375 
    376         public int getY() {
    377             return y;
    378         }
    379     }
    380 
    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 
    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 
    406     public boolean hasResizeFrames() {
    407         return mResizeFrames.size() > 0;
    408     }
    409 
    410     public boolean isWidgetBeingResized() {
    411         return mCurrentResizeFrame != null;
    412     }
    413 
    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 
    428     public void animateViewIntoPosition(DragView dragView, final View child) {
    429         animateViewIntoPosition(dragView, child, null);
    430     }
    431 
    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 
    444     public void animateViewIntoPosition(DragView dragView, final View child,
    445             final Runnable onFinishAnimationRunnable) {
    446         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
    447     }
    448 
    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 
    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      */
    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 
    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 
    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 
    663     public View getAnimatedView() {
    664         return mDropView;
    665     }
    666 
    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
    693     public void onChildViewAdded(View parent, View child) {
    694         updateChildIndices();
    695     }
    696 
    697     @Override
    698     public void onChildViewRemoved(View parent, View child) {
    699         updateChildIndices();
    700     }
    701 
    702     private void updateChildIndices() {
    703         if (mLauncher != null) {
    704             mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
    705             mQsbIndex = indexOfChild(mLauncher.getSearchBar());
    706         }
    707     }
    708 
    709     @Override
    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 
    735     void onEnterScrollArea(int direction) {
    736         mInScrollArea = true;
    737         invalidate();
    738     }
    739 
    740     void onExitScrollArea() {
    741         mInScrollArea = false;
    742         invalidate();
    743     }
    744 
    745     @Override
    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