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