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