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