DragLayer.java revision eecf02da58adef5486bf0c9ff7127ea891f457a4
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
17package com.android.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.util.AttributeSet;
31import android.view.KeyEvent;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.ViewParent;
35import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.AccessibilityManager;
37import android.view.animation.DecelerateInterpolator;
38import android.view.animation.Interpolator;
39import android.widget.FrameLayout;
40import android.widget.TextView;
41
42import com.android.launcher.R;
43
44import java.util.ArrayList;
45
46/**
47 * A ViewGroup that coordinates dragging across its descendants
48 */
49public class DragLayer extends FrameLayout {
50    private DragController mDragController;
51    private int[] mTmpXY = new int[2];
52
53    private int mXDown, mYDown;
54    private Launcher mLauncher;
55
56    // Variables relating to resizing widgets
57    private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
58            new ArrayList<AppWidgetResizeFrame>();
59    private AppWidgetResizeFrame mCurrentResizeFrame;
60
61    // Variables relating to animation of views after drop
62    private ValueAnimator mDropAnim = null;
63    private ValueAnimator mFadeOutAnim = null;
64    private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
65    private DragView mDropView = null;
66    private int mAnchorViewInitialScrollX = 0;
67    private View mAnchorView = null;
68
69    private boolean mHoverPointClosesFolder = false;
70    private Rect mHitRect = new Rect();
71    private int mWorkspaceIndex = -1;
72    private int mQsbIndex = -1;
73    public static final int ANIMATION_END_DISAPPEAR = 0;
74    public static final int ANIMATION_END_FADE_OUT = 1;
75    public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
76
77    /**
78     * Used to create a new DragLayer from XML.
79     *
80     * @param context The application's context.
81     * @param attrs The attributes set containing the Workspace's customization values.
82     */
83    public DragLayer(Context context, AttributeSet attrs) {
84        super(context, attrs);
85
86        // Disable multitouch across the workspace/all apps/customize tray
87        setMotionEventSplittingEnabled(false);
88        setChildrenDrawingOrderEnabled(true);
89    }
90
91    public void setup(Launcher launcher, DragController controller) {
92        mLauncher = launcher;
93        mDragController = controller;
94    }
95
96    @Override
97    public boolean dispatchKeyEvent(KeyEvent event) {
98        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
99    }
100
101    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
102        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
103        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
104            return true;
105        }
106        return false;
107    }
108
109    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
110        getDescendantRectRelativeToSelf(folder, mHitRect);
111        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
112            return true;
113        }
114        return false;
115    }
116
117    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
118        Rect hitRect = new Rect();
119        int x = (int) ev.getX();
120        int y = (int) ev.getY();
121
122        for (AppWidgetResizeFrame child: mResizeFrames) {
123            child.getHitRect(hitRect);
124            if (hitRect.contains(x, y)) {
125                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
126                    mCurrentResizeFrame = child;
127                    mXDown = x;
128                    mYDown = y;
129                    requestDisallowInterceptTouchEvent(true);
130                    return true;
131                }
132            }
133        }
134
135        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
136        if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
137            if (currentFolder.isEditingName()) {
138                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
139                    currentFolder.dismissEditingName();
140                    return true;
141                }
142            }
143
144            getDescendantRectRelativeToSelf(currentFolder, hitRect);
145            if (!isEventOverFolder(currentFolder, ev)) {
146                mLauncher.closeFolder();
147                return true;
148            }
149        }
150        return false;
151    }
152
153    @Override
154    public boolean onInterceptTouchEvent(MotionEvent ev) {
155        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
156            if (handleTouchDown(ev, true)) {
157                return true;
158            }
159        }
160        clearAllResizeFrames();
161        return mDragController.onInterceptTouchEvent(ev);
162    }
163
164    @Override
165    public boolean onInterceptHoverEvent(MotionEvent ev) {
166        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
167        if (currentFolder == null) {
168            return false;
169        } else {
170            if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
171                final int action = ev.getAction();
172                boolean isOverFolder;
173                switch (action) {
174                    case MotionEvent.ACTION_HOVER_ENTER:
175                        isOverFolder = isEventOverFolder(currentFolder, ev);
176                        if (!isOverFolder) {
177                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
178                            mHoverPointClosesFolder = true;
179                            return true;
180                        } else if (isOverFolder) {
181                            mHoverPointClosesFolder = false;
182                        } else {
183                            return true;
184                        }
185                    case MotionEvent.ACTION_HOVER_MOVE:
186                        isOverFolder = isEventOverFolder(currentFolder, ev);
187                        if (!isOverFolder && !mHoverPointClosesFolder) {
188                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
189                            mHoverPointClosesFolder = true;
190                            return true;
191                        } else if (isOverFolder) {
192                            mHoverPointClosesFolder = false;
193                        } else {
194                            return true;
195                        }
196                }
197            }
198        }
199        return false;
200    }
201
202    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
203        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
204            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
205            AccessibilityEvent event = AccessibilityEvent.obtain(
206                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
207            onInitializeAccessibilityEvent(event);
208            event.getText().add(mContext.getString(stringId));
209            AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
210        }
211    }
212
213    @Override
214    public boolean onHoverEvent(MotionEvent ev) {
215        // If we've received this, we've already done the necessary handling
216        // in onInterceptHoverEvent. Return true to consume the event.
217        return false;
218    }
219
220    @Override
221    public boolean onTouchEvent(MotionEvent ev) {
222        boolean handled = false;
223        int action = ev.getAction();
224
225        int x = (int) ev.getX();
226        int y = (int) ev.getY();
227
228        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
229            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
230                if (handleTouchDown(ev, false)) {
231                    return true;
232                }
233            }
234        }
235
236        if (mCurrentResizeFrame != null) {
237            handled = true;
238            switch (action) {
239                case MotionEvent.ACTION_MOVE:
240                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
241                    break;
242                case MotionEvent.ACTION_CANCEL:
243                case MotionEvent.ACTION_UP:
244                    mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown);
245                    mCurrentResizeFrame = null;
246            }
247        }
248        if (handled) return true;
249        return mDragController.onTouchEvent(ev);
250    }
251
252    /**
253     * Determine the rect of the descendant in this DragLayer's coordinates
254     *
255     * @param descendant The descendant whose coordinates we want to find.
256     * @param r The rect into which to place the results.
257     * @return The factor by which this descendant is scaled relative to this DragLayer.
258     */
259    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
260        mTmpXY[0] = 0;
261        mTmpXY[1] = 0;
262        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
263        r.set(mTmpXY[0], mTmpXY[1],
264                mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
265        return scale;
266    }
267
268    public void getLocationInDragLayer(View child, int[] loc) {
269        loc[0] = 0;
270        loc[1] = 0;
271        getDescendantCoordRelativeToSelf(child, loc);
272    }
273
274    /**
275     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
276     * coordinates.
277     *
278     * @param descendant The descendant to which the passed coordinate is relative.
279     * @param coord The coordinate that we want mapped.
280     * @return The factor by which this descendant is scaled relative to this DragLayer.
281     */
282    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
283        float scale = 1.0f;
284        float[] pt = {coord[0], coord[1]};
285        descendant.getMatrix().mapPoints(pt);
286        scale *= descendant.getScaleX();
287        pt[0] += descendant.getLeft();
288        pt[1] += descendant.getTop();
289        ViewParent viewParent = descendant.getParent();
290        while (viewParent instanceof View && viewParent != this) {
291            final View view = (View)viewParent;
292            view.getMatrix().mapPoints(pt);
293            scale *= view.getScaleX();
294            pt[0] += view.getLeft() - view.getScrollX();
295            pt[1] += view.getTop() - view.getScrollY();
296            viewParent = view.getParent();
297        }
298        coord[0] = (int) Math.round(pt[0]);
299        coord[1] = (int) Math.round(pt[1]);
300        return scale;
301    }
302
303    public void getViewRectRelativeToSelf(View v, Rect r) {
304        int[] loc = new int[2];
305        getLocationInWindow(loc);
306        int x = loc[0];
307        int y = loc[1];
308
309        v.getLocationInWindow(loc);
310        int vX = loc[0];
311        int vY = loc[1];
312
313        int left = vX - x;
314        int top = vY - y;
315        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
316    }
317
318    @Override
319    public boolean dispatchUnhandledMove(View focused, int direction) {
320        return mDragController.dispatchUnhandledMove(focused, direction);
321    }
322
323    public static class LayoutParams extends FrameLayout.LayoutParams {
324        public int x, y;
325        public boolean customPosition = false;
326
327        /**
328         * {@inheritDoc}
329         */
330        public LayoutParams(int width, int height) {
331            super(width, height);
332        }
333
334        public void setWidth(int width) {
335            this.width = width;
336        }
337
338        public int getWidth() {
339            return width;
340        }
341
342        public void setHeight(int height) {
343            this.height = height;
344        }
345
346        public int getHeight() {
347            return height;
348        }
349
350        public void setX(int x) {
351            this.x = x;
352        }
353
354        public int getX() {
355            return x;
356        }
357
358        public void setY(int y) {
359            this.y = y;
360        }
361
362        public int getY() {
363            return y;
364        }
365    }
366
367    protected void onLayout(boolean changed, int l, int t, int r, int b) {
368        super.onLayout(changed, l, t, r, b);
369        int count = getChildCount();
370        for (int i = 0; i < count; i++) {
371            View child = getChildAt(i);
372            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
373            if (flp instanceof LayoutParams) {
374                final LayoutParams lp = (LayoutParams) flp;
375                if (lp.customPosition) {
376                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
377                }
378            }
379        }
380    }
381
382    public void clearAllResizeFrames() {
383        if (mResizeFrames.size() > 0) {
384            for (AppWidgetResizeFrame frame: mResizeFrames) {
385                removeView(frame);
386            }
387            mResizeFrames.clear();
388        }
389    }
390
391    public boolean hasResizeFrames() {
392        return mResizeFrames.size() > 0;
393    }
394
395    public boolean isWidgetBeingResized() {
396        return mCurrentResizeFrame != null;
397    }
398
399    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
400            CellLayout cellLayout) {
401        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
402                itemInfo, widget, cellLayout, this);
403
404        LayoutParams lp = new LayoutParams(-1, -1);
405        lp.customPosition = true;
406
407        addView(resizeFrame, lp);
408        mResizeFrames.add(resizeFrame);
409
410        resizeFrame.snapToWidget(false);
411    }
412
413    public void animateViewIntoPosition(DragView dragView, final View child) {
414        animateViewIntoPosition(dragView, child, null);
415    }
416
417    public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
418            float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
419            int duration) {
420        Rect r = new Rect();
421        getViewRectRelativeToSelf(dragView, r);
422        final int fromX = r.left;
423        final int fromY = r.top;
424
425        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
426                onFinishRunnable, animationEndStyle, duration, null);
427    }
428
429    public void animateViewIntoPosition(DragView dragView, final View child,
430            final Runnable onFinishAnimationRunnable) {
431        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
432    }
433
434    public void animateViewIntoPosition(DragView dragView, final View child, int duration,
435            final Runnable onFinishAnimationRunnable, View anchorView) {
436        CellLayoutChildren parentChildren = (CellLayoutChildren) child.getParent();
437        CellLayout parent = (CellLayout) (CellLayout) parentChildren.getParent();
438        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
439        parentChildren.measureChild(child);
440
441        Rect r = new Rect();
442        getViewRectRelativeToSelf(dragView, r);
443
444        int coord[] = new int[2];
445        coord[0] = lp.x;
446        coord[1] = lp.y;
447
448        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
449        // the correct coordinates (above) and use these to determine the final location
450        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
451        int toX = coord[0];
452        int toY = coord[1];
453        if (child instanceof TextView) {
454            float childrenScale = parent.getChildrenScale();
455            TextView tv = (TextView) child;
456
457            // The child may be scaled (always about the center of the view) so to account for it,
458            // we have to offset the position by the scaled size.  Once we do that, we can center
459            // the drag view about the scaled child view.
460            toY += Math.round(((1f - childrenScale) * child.getMeasuredHeight()) / 2 +
461                    scale * childrenScale * tv.getPaddingTop());
462            toY -= dragView.getMeasuredHeight() * (1 - scale * childrenScale) / 2;
463            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
464
465            scale *= childrenScale;
466        } else if (child instanceof FolderIcon) {
467            // Account for holographic blur padding on the drag view
468            toY -= Workspace.DRAG_BITMAP_PADDING / 2;
469            // Center in the x coordinate about the target's drawable
470            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
471        } else {
472            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
473            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
474                    - child.getMeasuredWidth()))) / 2;
475        }
476
477        final int fromX = r.left;
478        final int fromY = r.top;
479        child.setVisibility(INVISIBLE);
480        Runnable onCompleteRunnable = new Runnable() {
481            public void run() {
482                child.setVisibility(VISIBLE);
483                if (onFinishAnimationRunnable != null) {
484                    onFinishAnimationRunnable.run();
485                }
486            }
487        };
488        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
489                onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
490    }
491
492    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
493            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
494            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
495            int animationEndStyle, int duration, View anchorView) {
496        Rect from = new Rect(fromX, fromY, fromX +
497                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
498        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
499        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
500                null, null, onCompleteRunnable, animationEndStyle, anchorView);
501    }
502
503    /**
504     * This method animates a view at the end of a drag and drop animation.
505     *
506     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
507     *        doesn't need to be a child of DragLayer.
508     * @param from The initial location of the view. Only the left and top parameters are used.
509     * @param to The final location of the view. Only the left and top parameters are used. This
510     *        location doesn't account for scaling, and so should be centered about the desired
511     *        final location (including scaling).
512     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
513     * @param finalScale The final scale of the view. The view is scaled about its center.
514     * @param duration The duration of the animation.
515     * @param motionInterpolator The interpolator to use for the location of the view.
516     * @param alphaInterpolator The interpolator to use for the alpha of the view.
517     * @param onCompleteRunnable Optional runnable to run on animation completion.
518     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
519     *        the runnable will execute after the view is faded out.
520     * @param anchorView If not null, this represents the view which the animated view stays
521     *        anchored to in case scrolling is currently taking place. Note: currently this is
522     *        only used for the X dimension for the case of the workspace.
523     */
524    public void animateView(final DragView view, final Rect from, final Rect to,
525            final float finalAlpha, final float initScaleX, final float initScaleY,
526            final float finalScaleX, final float finalScaleY, int duration,
527            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
528            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
529
530        // Calculate the duration of the animation based on the object's distance
531        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
532                Math.pow(to.top - from.top, 2));
533        final Resources res = getResources();
534        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
535
536        // If duration < 0, this is a cue to compute the duration based on the distance
537        if (duration < 0) {
538            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
539            if (dist < maxDist) {
540                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
541            }
542            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
543        }
544
545        if (mDropAnim != null) {
546            mDropAnim.cancel();
547        }
548
549        if (mFadeOutAnim != null) {
550            mFadeOutAnim.cancel();
551        }
552
553        // Show the drop view if it was previously hidden
554        mDropView = view;
555        mDropView.cancelAnimation();
556        mDropView.resetLayoutParams();
557        mDropAnim = new ValueAnimator();
558        if (alphaInterpolator == null || motionInterpolator == null) {
559            mDropAnim.setInterpolator(mCubicEaseOutInterpolator);
560        }
561
562        if (anchorView != null) {
563            mAnchorViewInitialScrollX = anchorView.getScrollX();
564        }
565        mAnchorView = anchorView;
566
567        final float initAlpha = view.getAlpha();
568        final float dropViewScale = mDropView.getScaleX();
569
570        mDropAnim.setDuration(duration);
571        mDropAnim.setFloatValues(0.0f, 1.0f);
572        mDropAnim.removeAllUpdateListeners();
573        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
574            public void onAnimationUpdate(ValueAnimator animation) {
575                final float percent = (Float) animation.getAnimatedValue();
576                final int width = view.getMeasuredWidth();
577                final int height = view.getMeasuredHeight();
578
579                float alphaPercent = alphaInterpolator == null ? percent :
580                        alphaInterpolator.getInterpolation(percent);
581                float motionPercent = motionInterpolator == null ? percent :
582                        motionInterpolator.getInterpolation(percent);
583
584                float initialScaleX = initScaleX * dropViewScale;
585                float initialScaleY = initScaleY * dropViewScale;
586                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
587                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
588                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
589
590                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
591                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
592
593                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
594                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
595
596                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
597                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
598                int yPos = y - mDropView.getScrollY();
599
600                mDropView.setTranslationX(xPos);
601                mDropView.setTranslationY(yPos);
602                mDropView.setScaleX(scaleX);
603                mDropView.setScaleY(scaleY);
604                mDropView.setAlpha(alpha);
605            }
606        });
607        mDropAnim.addListener(new AnimatorListenerAdapter() {
608            public void onAnimationEnd(Animator animation) {
609                if (onCompleteRunnable != null) {
610                    onCompleteRunnable.run();
611                }
612                switch (animationEndStyle) {
613                case ANIMATION_END_DISAPPEAR:
614                    clearAnimatedView();
615                    break;
616                case ANIMATION_END_FADE_OUT:
617                    fadeOutDragView();
618                    break;
619                case ANIMATION_END_REMAIN_VISIBLE:
620                    break;
621                }
622            }
623        });
624        mDropAnim.start();
625    }
626
627    public void clearAnimatedView() {
628        if (mDropAnim != null) {
629            mDropAnim.cancel();
630        }
631        if (mDropView != null) {
632            mDropView.remove();
633        }
634        mDropView = null;
635        invalidate();
636    }
637
638    public View getAnimatedView() {
639        return mDropView;
640    }
641
642    private void fadeOutDragView() {
643        mFadeOutAnim = new ValueAnimator();
644        mFadeOutAnim.setDuration(150);
645        mFadeOutAnim.setFloatValues(0f, 1f);
646        mFadeOutAnim.removeAllUpdateListeners();
647        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
648            public void onAnimationUpdate(ValueAnimator animation) {
649                final float percent = (Float) animation.getAnimatedValue();
650
651                float alpha = 1 - percent;
652                mDropView.setAlpha(alpha);
653            }
654        });
655        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
656            public void onAnimationEnd(Animator animation) {
657                if (mDropView != null) {
658                    mDropView.remove();
659                }
660                mDropView = null;
661                invalidate();
662            }
663        });
664        mFadeOutAnim.start();
665    }
666
667    @Override
668    protected void onViewAdded(View child) {
669        super.onViewAdded(child);
670        updateChildIndices();
671    }
672
673    @Override
674    protected void onViewRemoved(View child) {
675        super.onViewRemoved(child);
676        updateChildIndices();
677    }
678
679    private void updateChildIndices() {
680        if (mLauncher != null) {
681            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
682            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
683        }
684    }
685
686    @Override
687    protected int getChildDrawingOrder(int childCount, int i) {
688        // We don't want to prioritize the workspace drawing on top of the other children in
689        // landscape for the overscroll event.
690        if (LauncherApplication.isScreenLandscape(getContext())) {
691            return super.getChildDrawingOrder(childCount, i);
692        }
693
694        if (mWorkspaceIndex == -1 || mQsbIndex == -1 ||
695                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
696            return i;
697        }
698
699        // This ensures that the workspace is drawn above the hotseat and qsb,
700        // except when the workspace is drawing a background gradient, in which
701        // case we want the workspace to stay behind these elements.
702        if (i == mQsbIndex) {
703            return mWorkspaceIndex;
704        } else if (i == mWorkspaceIndex) {
705            return mQsbIndex;
706        } else {
707            return i;
708        }
709    }
710}
711