Home | History | Annotate | Download | only in launcher3
      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.launcher3;
     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.accessibility.AccessibilityEvent;
     35 import android.view.accessibility.AccessibilityManager;
     36 import android.view.animation.DecelerateInterpolator;
     37 import android.view.animation.Interpolator;
     38 import android.widget.FrameLayout;
     39 import android.widget.TextView;
     40 
     41 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
     42 
     43 import java.util.ArrayList;
     44 
     45 /**
     46  * A ViewGroup that coordinates dragging across its descendants
     47  */
     48 public class DragLayer extends InsettableFrameLayout {
     49     private DragController mDragController;
     50     private int[] mTmpXY = new int[2];
     51 
     52     private int mXDown, mYDown;
     53     private Launcher mLauncher;
     54 
     55     // Variables relating to resizing widgets
     56     private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
     57             new ArrayList<AppWidgetResizeFrame>();
     58     private AppWidgetResizeFrame mCurrentResizeFrame;
     59 
     60     // Variables relating to animation of views after drop
     61     private ValueAnimator mDropAnim = null;
     62     private ValueAnimator mFadeOutAnim = null;
     63     private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
     64     private DragView mDropView = null;
     65     private int mAnchorViewInitialScrollX = 0;
     66     private View mAnchorView = null;
     67 
     68     private boolean mHoverPointClosesFolder = false;
     69     private Rect mHitRect = new Rect();
     70     public static final int ANIMATION_END_DISAPPEAR = 0;
     71     public static final int ANIMATION_END_FADE_OUT = 1;
     72     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
     73 
     74     private TouchCompleteListener mTouchCompleteListener;
     75 
     76     private View mOverlayView;
     77     private int mTopViewIndex;
     78     private int mChildCountOnLastUpdate = -1;
     79 
     80     // Darkening scrim
     81     private Drawable mBackground;
     82     private float mBackgroundAlpha = 0;
     83 
     84     // Related to adjacent page hints
     85     private boolean mInScrollArea;
     86     private boolean mShowPageHints;
     87     private Drawable mLeftHoverDrawable;
     88     private Drawable mRightHoverDrawable;
     89     private Drawable mLeftHoverDrawableActive;
     90     private Drawable mRightHoverDrawableActive;
     91 
     92     private boolean mBlockTouches = false;
     93 
     94     /**
     95      * Used to create a new DragLayer from XML.
     96      *
     97      * @param context The application's context.
     98      * @param attrs The attributes set containing the Workspace's customization values.
     99      */
    100     public DragLayer(Context context, AttributeSet attrs) {
    101         super(context, attrs);
    102 
    103         // Disable multitouch across the workspace/all apps/customize tray
    104         setMotionEventSplittingEnabled(false);
    105         setChildrenDrawingOrderEnabled(true);
    106 
    107         final Resources res = getResources();
    108         mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
    109         mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
    110         mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
    111         mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
    112         mBackground = res.getDrawable(R.drawable.apps_customize_bg);
    113     }
    114 
    115     public void setup(Launcher launcher, DragController controller) {
    116         mLauncher = launcher;
    117         mDragController = controller;
    118     }
    119 
    120     @Override
    121     public boolean dispatchKeyEvent(KeyEvent event) {
    122         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
    123     }
    124 
    125     public void showOverlayView(View overlayView) {
    126         LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    127         mOverlayView = overlayView;
    128         addView(overlayView, lp);
    129 
    130         // ensure that the overlay view stays on top. we can't use drawing order for this
    131         // because in API level 16 touch dispatch doesn't respect drawing order.
    132         mOverlayView.bringToFront();
    133     }
    134 
    135     public void dismissOverlayView() {
    136         removeView(mOverlayView);
    137     }
    138 
    139     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
    140         getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
    141         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
    142             return true;
    143         }
    144         return false;
    145     }
    146 
    147     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
    148         getDescendantRectRelativeToSelf(folder, mHitRect);
    149         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
    150             return true;
    151         }
    152         return false;
    153     }
    154 
    155     public void setBlockTouch(boolean block) {
    156         mBlockTouches = block;
    157     }
    158 
    159     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
    160         Rect hitRect = new Rect();
    161         int x = (int) ev.getX();
    162         int y = (int) ev.getY();
    163 
    164         if (mBlockTouches) {
    165             return true;
    166         }
    167 
    168         for (AppWidgetResizeFrame child: mResizeFrames) {
    169             child.getHitRect(hitRect);
    170             if (hitRect.contains(x, y)) {
    171                 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
    172                     mCurrentResizeFrame = child;
    173                     mXDown = x;
    174                     mYDown = y;
    175                     requestDisallowInterceptTouchEvent(true);
    176                     return true;
    177                 }
    178             }
    179         }
    180 
    181         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
    182         if (currentFolder != null && intercept) {
    183             if (currentFolder.isEditingName()) {
    184                 if (!isEventOverFolderTextRegion(currentFolder, ev)) {
    185                     currentFolder.dismissEditingName();
    186                     return true;
    187                 }
    188             }
    189 
    190             getDescendantRectRelativeToSelf(currentFolder, hitRect);
    191             if (!isEventOverFolder(currentFolder, ev)) {
    192                 mLauncher.closeFolder();
    193                 return true;
    194             }
    195         }
    196         return false;
    197     }
    198 
    199     @Override
    200     public boolean onInterceptTouchEvent(MotionEvent ev) {
    201         int action = ev.getAction();
    202 
    203         if (action == MotionEvent.ACTION_DOWN) {
    204             if (handleTouchDown(ev, true)) {
    205                 return true;
    206             }
    207         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
    208             if (mTouchCompleteListener != null) {
    209                 mTouchCompleteListener.onTouchComplete();
    210             }
    211             mTouchCompleteListener = null;
    212         }
    213         clearAllResizeFrames();
    214         return mDragController.onInterceptTouchEvent(ev);
    215     }
    216 
    217     @Override
    218     public boolean onInterceptHoverEvent(MotionEvent ev) {
    219         if (mLauncher == null || mLauncher.getWorkspace() == null) {
    220             return false;
    221         }
    222         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
    223         if (currentFolder == null) {
    224             return false;
    225         } else {
    226                 AccessibilityManager accessibilityManager = (AccessibilityManager)
    227                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
    228             if (accessibilityManager.isTouchExplorationEnabled()) {
    229                 final int action = ev.getAction();
    230                 boolean isOverFolder;
    231                 switch (action) {
    232                     case MotionEvent.ACTION_HOVER_ENTER:
    233                         isOverFolder = isEventOverFolder(currentFolder, ev);
    234                         if (!isOverFolder) {
    235                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
    236                             mHoverPointClosesFolder = true;
    237                             return true;
    238                         }
    239                         mHoverPointClosesFolder = false;
    240                         break;
    241                     case MotionEvent.ACTION_HOVER_MOVE:
    242                         isOverFolder = isEventOverFolder(currentFolder, ev);
    243                         if (!isOverFolder && !mHoverPointClosesFolder) {
    244                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
    245                             mHoverPointClosesFolder = true;
    246                             return true;
    247                         } else if (!isOverFolder) {
    248                             return true;
    249                         }
    250                         mHoverPointClosesFolder = false;
    251                 }
    252             }
    253         }
    254         return false;
    255     }
    256 
    257     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
    258         AccessibilityManager accessibilityManager = (AccessibilityManager)
    259                 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
    260         if (accessibilityManager.isEnabled()) {
    261             int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
    262             AccessibilityEvent event = AccessibilityEvent.obtain(
    263                     AccessibilityEvent.TYPE_VIEW_FOCUSED);
    264             onInitializeAccessibilityEvent(event);
    265             event.getText().add(getContext().getString(stringId));
    266             accessibilityManager.sendAccessibilityEvent(event);
    267         }
    268     }
    269 
    270     @Override
    271     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    272         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
    273         if (currentFolder != null) {
    274             if (child == currentFolder) {
    275                 return super.onRequestSendAccessibilityEvent(child, event);
    276             }
    277             // Skip propagating onRequestSendAccessibilityEvent all for other children
    278             // when a folder is open
    279             return false;
    280         }
    281         return super.onRequestSendAccessibilityEvent(child, event);
    282     }
    283 
    284     @Override
    285     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
    286         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
    287         if (currentFolder != null) {
    288             // Only add the folder as a child for accessibility when it is open
    289             childrenForAccessibility.add(currentFolder);
    290         } else {
    291             super.addChildrenForAccessibility(childrenForAccessibility);
    292         }
    293     }
    294 
    295     @Override
    296     public boolean onHoverEvent(MotionEvent ev) {
    297         // If we've received this, we've already done the necessary handling
    298         // in onInterceptHoverEvent. Return true to consume the event.
    299         return false;
    300     }
    301 
    302     @Override
    303     public boolean onTouchEvent(MotionEvent ev) {
    304         boolean handled = false;
    305         int action = ev.getAction();
    306 
    307         int x = (int) ev.getX();
    308         int y = (int) ev.getY();
    309 
    310         if (mBlockTouches) {
    311             return true;
    312         }
    313 
    314         if (action == MotionEvent.ACTION_DOWN) {
    315             if (handleTouchDown(ev, false)) {
    316                 return true;
    317             }
    318         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
    319             if (mTouchCompleteListener != null) {
    320                 mTouchCompleteListener.onTouchComplete();
    321             }
    322             mTouchCompleteListener = null;
    323         }
    324 
    325         if (mCurrentResizeFrame != null) {
    326             handled = true;
    327             switch (action) {
    328                 case MotionEvent.ACTION_MOVE:
    329                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
    330                     break;
    331                 case MotionEvent.ACTION_CANCEL:
    332                 case MotionEvent.ACTION_UP:
    333                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
    334                     mCurrentResizeFrame.onTouchUp();
    335                     mCurrentResizeFrame = null;
    336             }
    337         }
    338         if (handled) return true;
    339         return mDragController.onTouchEvent(ev);
    340     }
    341 
    342     /**
    343      * Determine the rect of the descendant in this DragLayer's coordinates
    344      *
    345      * @param descendant The descendant whose coordinates we want to find.
    346      * @param r The rect into which to place the results.
    347      * @return The factor by which this descendant is scaled relative to this DragLayer.
    348      */
    349     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
    350         mTmpXY[0] = 0;
    351         mTmpXY[1] = 0;
    352         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
    353 
    354         r.set(mTmpXY[0], mTmpXY[1],
    355                 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
    356                 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
    357         return scale;
    358     }
    359 
    360     public float getLocationInDragLayer(View child, int[] loc) {
    361         loc[0] = 0;
    362         loc[1] = 0;
    363         return getDescendantCoordRelativeToSelf(child, loc);
    364     }
    365 
    366     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
    367         return getDescendantCoordRelativeToSelf(descendant, coord, false);
    368     }
    369 
    370     /**
    371      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
    372      * coordinates.
    373      *
    374      * @param descendant The descendant to which the passed coordinate is relative.
    375      * @param coord The coordinate that we want mapped.
    376      * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
    377      *          sometimes this is relevant as in a child's coordinates within the root descendant.
    378      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
    379      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
    380      *         assumption fails, we will need to return a pair of scale factors.
    381      */
    382     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
    383             boolean includeRootScroll) {
    384         return Utilities.getDescendantCoordRelativeToParent(descendant, this,
    385                 coord, includeRootScroll);
    386     }
    387 
    388     /**
    389      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
    390      */
    391     public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
    392         return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
    393     }
    394 
    395     public void getViewRectRelativeToSelf(View v, Rect r) {
    396         int[] loc = new int[2];
    397         getLocationInWindow(loc);
    398         int x = loc[0];
    399         int y = loc[1];
    400 
    401         v.getLocationInWindow(loc);
    402         int vX = loc[0];
    403         int vY = loc[1];
    404 
    405         int left = vX - x;
    406         int top = vY - y;
    407         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
    408     }
    409 
    410     @Override
    411     public boolean dispatchUnhandledMove(View focused, int direction) {
    412         return mDragController.dispatchUnhandledMove(focused, direction);
    413     }
    414 
    415     @Override
    416     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    417         return new LayoutParams(getContext(), attrs);
    418     }
    419 
    420     @Override
    421     protected LayoutParams generateDefaultLayoutParams() {
    422         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    423     }
    424 
    425     // Override to allow type-checking of LayoutParams.
    426     @Override
    427     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    428         return p instanceof LayoutParams;
    429     }
    430 
    431     @Override
    432     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    433         return new LayoutParams(p);
    434     }
    435 
    436     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
    437         public int x, y;
    438         public boolean customPosition = false;
    439 
    440         public LayoutParams(Context c, AttributeSet attrs) {
    441             super(c, attrs);
    442         }
    443 
    444         public LayoutParams(int width, int height) {
    445             super(width, height);
    446         }
    447 
    448         public LayoutParams(ViewGroup.LayoutParams lp) {
    449             super(lp);
    450         }
    451 
    452         public void setWidth(int width) {
    453             this.width = width;
    454         }
    455 
    456         public int getWidth() {
    457             return width;
    458         }
    459 
    460         public void setHeight(int height) {
    461             this.height = height;
    462         }
    463 
    464         public int getHeight() {
    465             return height;
    466         }
    467 
    468         public void setX(int x) {
    469             this.x = x;
    470         }
    471 
    472         public int getX() {
    473             return x;
    474         }
    475 
    476         public void setY(int y) {
    477             this.y = y;
    478         }
    479 
    480         public int getY() {
    481             return y;
    482         }
    483     }
    484 
    485     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    486         super.onLayout(changed, l, t, r, b);
    487         int count = getChildCount();
    488         for (int i = 0; i < count; i++) {
    489             View child = getChildAt(i);
    490             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
    491             if (flp instanceof LayoutParams) {
    492                 final LayoutParams lp = (LayoutParams) flp;
    493                 if (lp.customPosition) {
    494                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
    495                 }
    496             }
    497         }
    498     }
    499 
    500     public void clearAllResizeFrames() {
    501         if (mResizeFrames.size() > 0) {
    502             for (AppWidgetResizeFrame frame: mResizeFrames) {
    503                 frame.commitResize();
    504                 removeView(frame);
    505             }
    506             mResizeFrames.clear();
    507         }
    508     }
    509 
    510     public boolean hasResizeFrames() {
    511         return mResizeFrames.size() > 0;
    512     }
    513 
    514     public boolean isWidgetBeingResized() {
    515         return mCurrentResizeFrame != null;
    516     }
    517 
    518     public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
    519             CellLayout cellLayout) {
    520         AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
    521                 widget, cellLayout, this);
    522 
    523         LayoutParams lp = new LayoutParams(-1, -1);
    524         lp.customPosition = true;
    525 
    526         addView(resizeFrame, lp);
    527         mResizeFrames.add(resizeFrame);
    528 
    529         resizeFrame.snapToWidget(false);
    530     }
    531 
    532     public void animateViewIntoPosition(DragView dragView, final View child) {
    533         animateViewIntoPosition(dragView, child, null, null);
    534     }
    535 
    536     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
    537             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
    538             int duration) {
    539         Rect r = new Rect();
    540         getViewRectRelativeToSelf(dragView, r);
    541         final int fromX = r.left;
    542         final int fromY = r.top;
    543 
    544         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
    545                 onFinishRunnable, animationEndStyle, duration, null);
    546     }
    547 
    548     public void animateViewIntoPosition(DragView dragView, final View child,
    549             final Runnable onFinishAnimationRunnable, View anchorView) {
    550         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
    551     }
    552 
    553     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
    554             final Runnable onFinishAnimationRunnable, View anchorView) {
    555         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
    556         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
    557         parentChildren.measureChild(child);
    558 
    559         Rect r = new Rect();
    560         getViewRectRelativeToSelf(dragView, r);
    561 
    562         int coord[] = new int[2];
    563         float childScale = child.getScaleX();
    564         coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
    565         coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
    566 
    567         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
    568         // the correct coordinates (above) and use these to determine the final location
    569         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
    570         // We need to account for the scale of the child itself, as the above only accounts for
    571         // for the scale in parents.
    572         scale *= childScale;
    573         int toX = coord[0];
    574         int toY = coord[1];
    575         float toScale = scale;
    576         if (child instanceof TextView) {
    577             TextView tv = (TextView) child;
    578             // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
    579             // the workspace may have smaller icon bounds).
    580             toScale = scale / dragView.getIntrinsicIconScaleFactor();
    581 
    582             // The child may be scaled (always about the center of the view) so to account for it,
    583             // we have to offset the position by the scaled size.  Once we do that, we can center
    584             // the drag view about the scaled child view.
    585             toY += Math.round(toScale * tv.getPaddingTop());
    586             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
    587             if (dragView.getDragVisualizeOffset() != null) {
    588                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
    589             }
    590 
    591             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
    592         } else if (child instanceof FolderIcon) {
    593             // Account for holographic blur padding on the drag view
    594             toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
    595             toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
    596             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
    597             // Center in the x coordinate about the target's drawable
    598             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
    599         } else {
    600             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
    601             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
    602                     - child.getMeasuredWidth()))) / 2;
    603         }
    604 
    605         final int fromX = r.left;
    606         final int fromY = r.top;
    607         child.setVisibility(INVISIBLE);
    608         Runnable onCompleteRunnable = new Runnable() {
    609             public void run() {
    610                 child.setVisibility(VISIBLE);
    611                 if (onFinishAnimationRunnable != null) {
    612                     onFinishAnimationRunnable.run();
    613                 }
    614             }
    615         };
    616         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
    617                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
    618     }
    619 
    620     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
    621             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
    622             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
    623             int animationEndStyle, int duration, View anchorView) {
    624         Rect from = new Rect(fromX, fromY, fromX +
    625                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
    626         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
    627         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
    628                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
    629     }
    630 
    631     /**
    632      * This method animates a view at the end of a drag and drop animation.
    633      *
    634      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
    635      *        doesn't need to be a child of DragLayer.
    636      * @param from The initial location of the view. Only the left and top parameters are used.
    637      * @param to The final location of the view. Only the left and top parameters are used. This
    638      *        location doesn't account for scaling, and so should be centered about the desired
    639      *        final location (including scaling).
    640      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
    641      * @param finalScale The final scale of the view. The view is scaled about its center.
    642      * @param duration The duration of the animation.
    643      * @param motionInterpolator The interpolator to use for the location of the view.
    644      * @param alphaInterpolator The interpolator to use for the alpha of the view.
    645      * @param onCompleteRunnable Optional runnable to run on animation completion.
    646      * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
    647      *        the runnable will execute after the view is faded out.
    648      * @param anchorView If not null, this represents the view which the animated view stays
    649      *        anchored to in case scrolling is currently taking place. Note: currently this is
    650      *        only used for the X dimension for the case of the workspace.
    651      */
    652     public void animateView(final DragView view, final Rect from, final Rect to,
    653             final float finalAlpha, final float initScaleX, final float initScaleY,
    654             final float finalScaleX, final float finalScaleY, int duration,
    655             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
    656             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
    657 
    658         // Calculate the duration of the animation based on the object's distance
    659         final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
    660                 Math.pow(to.top - from.top, 2));
    661         final Resources res = getResources();
    662         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
    663 
    664         // If duration < 0, this is a cue to compute the duration based on the distance
    665         if (duration < 0) {
    666             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
    667             if (dist < maxDist) {
    668                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
    669             }
    670             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
    671         }
    672 
    673         // Fall back to cubic ease out interpolator for the animation if none is specified
    674         TimeInterpolator interpolator = null;
    675         if (alphaInterpolator == null || motionInterpolator == null) {
    676             interpolator = mCubicEaseOutInterpolator;
    677         }
    678 
    679         // Animate the view
    680         final float initAlpha = view.getAlpha();
    681         final float dropViewScale = view.getScaleX();
    682         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
    683             @Override
    684             public void onAnimationUpdate(ValueAnimator animation) {
    685                 final float percent = (Float) animation.getAnimatedValue();
    686                 final int width = view.getMeasuredWidth();
    687                 final int height = view.getMeasuredHeight();
    688 
    689                 float alphaPercent = alphaInterpolator == null ? percent :
    690                         alphaInterpolator.getInterpolation(percent);
    691                 float motionPercent = motionInterpolator == null ? percent :
    692                         motionInterpolator.getInterpolation(percent);
    693 
    694                 float initialScaleX = initScaleX * dropViewScale;
    695                 float initialScaleY = initScaleY * dropViewScale;
    696                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
    697                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
    698                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
    699 
    700                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
    701                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
    702 
    703                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
    704                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
    705 
    706                 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
    707                     (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
    708 
    709                 int xPos = x - mDropView.getScrollX() + anchorAdjust;
    710                 int yPos = y - mDropView.getScrollY();
    711 
    712                 mDropView.setTranslationX(xPos);
    713                 mDropView.setTranslationY(yPos);
    714                 mDropView.setScaleX(scaleX);
    715                 mDropView.setScaleY(scaleY);
    716                 mDropView.setAlpha(alpha);
    717             }
    718         };
    719         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
    720                 anchorView);
    721     }
    722 
    723     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
    724             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
    725             final int animationEndStyle, View anchorView) {
    726         // Clean up the previous animations
    727         if (mDropAnim != null) mDropAnim.cancel();
    728         if (mFadeOutAnim != null) mFadeOutAnim.cancel();
    729 
    730         // Show the drop view if it was previously hidden
    731         mDropView = view;
    732         mDropView.cancelAnimation();
    733         mDropView.resetLayoutParams();
    734 
    735         // Set the anchor view if the page is scrolling
    736         if (anchorView != null) {
    737             mAnchorViewInitialScrollX = anchorView.getScrollX();
    738         }
    739         mAnchorView = anchorView;
    740 
    741         // Create and start the animation
    742         mDropAnim = new ValueAnimator();
    743         mDropAnim.setInterpolator(interpolator);
    744         mDropAnim.setDuration(duration);
    745         mDropAnim.setFloatValues(0f, 1f);
    746         mDropAnim.addUpdateListener(updateCb);
    747         mDropAnim.addListener(new AnimatorListenerAdapter() {
    748             public void onAnimationEnd(Animator animation) {
    749                 if (onCompleteRunnable != null) {
    750                     onCompleteRunnable.run();
    751                 }
    752                 switch (animationEndStyle) {
    753                 case ANIMATION_END_DISAPPEAR:
    754                     clearAnimatedView();
    755                     break;
    756                 case ANIMATION_END_FADE_OUT:
    757                     fadeOutDragView();
    758                     break;
    759                 case ANIMATION_END_REMAIN_VISIBLE:
    760                     break;
    761                 }
    762             }
    763         });
    764         mDropAnim.start();
    765     }
    766 
    767     public void clearAnimatedView() {
    768         if (mDropAnim != null) {
    769             mDropAnim.cancel();
    770         }
    771         if (mDropView != null) {
    772             mDragController.onDeferredEndDrag(mDropView);
    773         }
    774         mDropView = null;
    775         invalidate();
    776     }
    777 
    778     public View getAnimatedView() {
    779         return mDropView;
    780     }
    781 
    782     private void fadeOutDragView() {
    783         mFadeOutAnim = new ValueAnimator();
    784         mFadeOutAnim.setDuration(150);
    785         mFadeOutAnim.setFloatValues(0f, 1f);
    786         mFadeOutAnim.removeAllUpdateListeners();
    787         mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
    788             public void onAnimationUpdate(ValueAnimator animation) {
    789                 final float percent = (Float) animation.getAnimatedValue();
    790 
    791                 float alpha = 1 - percent;
    792                 mDropView.setAlpha(alpha);
    793             }
    794         });
    795         mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
    796             public void onAnimationEnd(Animator animation) {
    797                 if (mDropView != null) {
    798                     mDragController.onDeferredEndDrag(mDropView);
    799                 }
    800                 mDropView = null;
    801                 invalidate();
    802             }
    803         });
    804         mFadeOutAnim.start();
    805     }
    806 
    807     @Override
    808     public void onChildViewAdded(View parent, View child) {
    809         super.onChildViewAdded(parent, child);
    810         if (mOverlayView != null) {
    811             // ensure that the overlay view stays on top. we can't use drawing order for this
    812             // because in API level 16 touch dispatch doesn't respect drawing order.
    813             mOverlayView.bringToFront();
    814         }
    815         updateChildIndices();
    816     }
    817 
    818     @Override
    819     public void onChildViewRemoved(View parent, View child) {
    820         updateChildIndices();
    821     }
    822 
    823     @Override
    824     public void bringChildToFront(View child) {
    825         super.bringChildToFront(child);
    826         if (child != mOverlayView && mOverlayView != null) {
    827             // ensure that the overlay view stays on top. we can't use drawing order for this
    828             // because in API level 16 touch dispatch doesn't respect drawing order.
    829             mOverlayView.bringToFront();
    830         }
    831         updateChildIndices();
    832     }
    833 
    834     private void updateChildIndices() {
    835         mTopViewIndex = -1;
    836         int childCount = getChildCount();
    837         for (int i = 0; i < childCount; i++) {
    838             if (getChildAt(i) instanceof DragView) {
    839                 mTopViewIndex = i;
    840             }
    841         }
    842         mChildCountOnLastUpdate = childCount;
    843     }
    844 
    845     @Override
    846     protected int getChildDrawingOrder(int childCount, int i) {
    847         if (mChildCountOnLastUpdate != childCount) {
    848             // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
    849             // Pre-18, the child was not added / removed by the time of those callbacks. We need to
    850             // force update our representation of things here to avoid crashing on pre-18 devices
    851             // in certain instances.
    852             updateChildIndices();
    853         }
    854 
    855         // i represents the current draw iteration
    856         if (mTopViewIndex == -1) {
    857             // in general we do nothing
    858             return i;
    859         } else if (i == childCount - 1) {
    860             // if we have a top index, we return it when drawing last item (highest z-order)
    861             return mTopViewIndex;
    862         } else if (i < mTopViewIndex) {
    863             return i;
    864         } else {
    865             // for indexes greater than the top index, we fetch one item above to shift for the
    866             // displacement of the top index
    867             return i + 1;
    868         }
    869     }
    870 
    871     void onEnterScrollArea(int direction) {
    872         mInScrollArea = true;
    873         invalidate();
    874     }
    875 
    876     void onExitScrollArea() {
    877         mInScrollArea = false;
    878         invalidate();
    879     }
    880 
    881     void showPageHints() {
    882         mShowPageHints = true;
    883         invalidate();
    884     }
    885 
    886     void hidePageHints() {
    887         mShowPageHints = false;
    888         invalidate();
    889     }
    890 
    891     /**
    892      * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
    893      */
    894     private boolean isLayoutRtl() {
    895         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
    896     }
    897 
    898     @Override
    899     protected void dispatchDraw(Canvas canvas) {
    900         // Draw the background gradient below children.
    901         if (mBackground != null && mBackgroundAlpha > 0.0f) {
    902             int alpha = (int) (mBackgroundAlpha * 255);
    903             mBackground.setAlpha(alpha);
    904             mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
    905             mBackground.draw(canvas);
    906         }
    907 
    908         super.dispatchDraw(canvas);
    909     }
    910 
    911     private void drawPageHints(Canvas canvas) {
    912         if (mShowPageHints) {
    913             Workspace workspace = mLauncher.getWorkspace();
    914             int width = getMeasuredWidth();
    915             Rect childRect = new Rect();
    916             getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.getChildCount() - 1),
    917                     childRect);
    918 
    919             int page = workspace.getNextPage();
    920             final boolean isRtl = isLayoutRtl();
    921             CellLayout leftPage = (CellLayout) workspace.getChildAt(isRtl ? page + 1 : page - 1);
    922             CellLayout rightPage = (CellLayout) workspace.getChildAt(isRtl ? page - 1 : page + 1);
    923 
    924             if (leftPage != null && leftPage.isDragTarget()) {
    925                 Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
    926                         mLeftHoverDrawableActive : mLeftHoverDrawable;
    927                 left.setBounds(0, childRect.top,
    928                         left.getIntrinsicWidth(), childRect.bottom);
    929                 left.draw(canvas);
    930             }
    931             if (rightPage != null && rightPage.isDragTarget()) {
    932                 Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
    933                         mRightHoverDrawableActive : mRightHoverDrawable;
    934                 right.setBounds(width - right.getIntrinsicWidth(),
    935                         childRect.top, width, childRect.bottom);
    936                 right.draw(canvas);
    937             }
    938         }
    939     }
    940 
    941     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    942         boolean ret = super.drawChild(canvas, child, drawingTime);
    943 
    944         // We want to draw the page hints above the workspace, but below the drag view.
    945         if (child instanceof Workspace) {
    946             drawPageHints(canvas);
    947         }
    948         return ret;
    949     }
    950 
    951     public void setBackgroundAlpha(float alpha) {
    952         if (alpha != mBackgroundAlpha) {
    953             mBackgroundAlpha = alpha;
    954             invalidate();
    955         }
    956     }
    957 
    958     public float getBackgroundAlpha() {
    959         return mBackgroundAlpha;
    960     }
    961 
    962     public void setTouchCompleteListener(TouchCompleteListener listener) {
    963         mTouchCompleteListener = listener;
    964     }
    965 
    966     public interface TouchCompleteListener {
    967         public void onTouchComplete();
    968     }
    969 }
    970