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