DragLayer.java revision 7bd1bbb509f9569fa18d6b4d33242679fd98bc9b
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) child.getParent()).measureChild(child);
437        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
438
439        Rect r = new Rect();
440        getViewRectRelativeToSelf(dragView, r);
441
442        int coord[] = new int[2];
443        coord[0] = lp.x;
444        coord[1] = lp.y;
445        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
446        // the correct coordinates (above) and use these to determine the final location
447        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
448        int toX = coord[0];
449        int toY = coord[1];
450        if (child instanceof TextView) {
451            TextView tv = (TextView) child;
452            Drawable d = tv.getCompoundDrawables()[1];
453
454            // Center in the y coordinate about the target's drawable
455            toY += Math.round(scale * tv.getPaddingTop());
456            toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2;
457            // Center in the x coordinate about the target's drawable
458            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
459        } else if (child instanceof FolderIcon) {
460            // Account for holographic blur padding on the drag view
461            toY -= HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2;
462            // Center in the x coordinate about the target's drawable
463            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
464        } else {
465            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
466            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
467                    - child.getMeasuredWidth()))) / 2;
468        }
469
470        final int fromX = r.left;
471        final int fromY = r.top;
472        child.setVisibility(INVISIBLE);
473        child.setAlpha(0);
474        Runnable onCompleteRunnable = new Runnable() {
475            public void run() {
476                child.setVisibility(VISIBLE);
477                ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
478                oa.setDuration(60);
479                oa.addListener(new AnimatorListenerAdapter() {
480                    @Override
481                    public void onAnimationEnd(android.animation.Animator animation) {
482                        if (onFinishAnimationRunnable != null) {
483                            onFinishAnimationRunnable.run();
484                        }
485                    }
486                });
487                oa.start();
488            }
489        };
490        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
491                onCompleteRunnable, ANIMATION_END_FADE_OUT, duration, anchorView);
492    }
493
494    private void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
495            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
496            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
497            int animationEndStyle, int duration, View anchorView) {
498        Rect from = new Rect(fromX, fromY, fromX +
499                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
500        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
501        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
502                null, null, onCompleteRunnable, animationEndStyle, anchorView);
503    }
504
505    /**
506     * This method animates a view at the end of a drag and drop animation.
507     *
508     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
509     *        doesn't need to be a child of DragLayer.
510     * @param from The initial location of the view. Only the left and top parameters are used.
511     * @param to The final location of the view. Only the left and top parameters are used. This
512     *        location doesn't account for scaling, and so should be centered about the desired
513     *        final location (including scaling).
514     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
515     * @param finalScale The final scale of the view. The view is scaled about its center.
516     * @param duration The duration of the animation.
517     * @param motionInterpolator The interpolator to use for the location of the view.
518     * @param alphaInterpolator The interpolator to use for the alpha of the view.
519     * @param onCompleteRunnable Optional runnable to run on animation completion.
520     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
521     *        the runnable will execute after the view is faded out.
522     * @param anchorView If not null, this represents the view which the animated view stays
523     *        anchored to in case scrolling is currently taking place. Note: currently this is
524     *        only used for the X dimension for the case of the workspace.
525     */
526    public void animateView(final DragView view, final Rect from, final Rect to,
527            final float finalAlpha, final float initScaleX, final float initScaleY,
528            final float finalScaleX, final float finalScaleY, int duration,
529            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
530            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
531
532        // Calculate the duration of the animation based on the object's distance
533        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
534                Math.pow(to.top - from.top, 2));
535        final Resources res = getResources();
536        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
537
538        // If duration < 0, this is a cue to compute the duration based on the distance
539        if (duration < 0) {
540            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
541            if (dist < maxDist) {
542                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
543            }
544        }
545
546        if (mDropAnim != null) {
547            mDropAnim.cancel();
548        }
549
550        if (mFadeOutAnim != null) {
551            mFadeOutAnim.cancel();
552        }
553
554        // Show the drop view if it was previously hidden
555        mDropView = view;
556        mDropView.cancelAnimation();
557        mDropView.resetLayoutParams();
558        mDropAnim = new ValueAnimator();
559        if (alphaInterpolator == null || motionInterpolator == null) {
560            mDropAnim.setInterpolator(mCubicEaseOutInterpolator);
561        }
562
563        if (anchorView != null) {
564            mAnchorViewInitialScrollX = anchorView.getScrollX();
565        }
566        mAnchorView = anchorView;
567
568        final float initAlpha = view.getAlpha();
569        final float dropViewScale = mDropView.getScaleX();
570
571        mDropAnim.setDuration(duration);
572        mDropAnim.setFloatValues(0.0f, 1.0f);
573        mDropAnim.removeAllUpdateListeners();
574        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
575            public void onAnimationUpdate(ValueAnimator animation) {
576                final float percent = (Float) animation.getAnimatedValue();
577                final int width = view.getMeasuredWidth();
578                final int height = view.getMeasuredHeight();
579
580                float alphaPercent = alphaInterpolator == null ? percent :
581                        alphaInterpolator.getInterpolation(percent);
582                float motionPercent = motionInterpolator == null ? percent :
583                        motionInterpolator.getInterpolation(percent);
584
585                float initialScaleX = initScaleX * dropViewScale;
586                float initialScaleY = initScaleY * dropViewScale;
587                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
588                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
589                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
590
591                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
592                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
593
594                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
595                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
596
597                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
598                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
599                int yPos = y - mDropView.getScrollY();
600
601                mDropView.setTranslationX(xPos);
602                mDropView.setTranslationY(yPos);
603                mDropView.setScaleX(scaleX);
604                mDropView.setScaleY(scaleY);
605                mDropView.setAlpha(alpha);
606            }
607        });
608        mDropAnim.addListener(new AnimatorListenerAdapter() {
609            public void onAnimationEnd(Animator animation) {
610                if (onCompleteRunnable != null) {
611                    onCompleteRunnable.run();
612                }
613                switch (animationEndStyle) {
614                case ANIMATION_END_DISAPPEAR:
615                    clearAnimatedView();
616                    break;
617                case ANIMATION_END_FADE_OUT:
618                    fadeOutDragView();
619                    break;
620                case ANIMATION_END_REMAIN_VISIBLE:
621                    break;
622                }
623            }
624        });
625        mDropAnim.start();
626    }
627
628    public void clearAnimatedView() {
629        if (mDropAnim != null) {
630            mDropAnim.cancel();
631        }
632        if (mDropView != null) {
633            mDropView.remove();
634        }
635        mDropView = null;
636        invalidate();
637    }
638
639    public View getAnimatedView() {
640        return mDropView;
641    }
642
643    private void fadeOutDragView() {
644        mFadeOutAnim = new ValueAnimator();
645        mFadeOutAnim.setDuration(150);
646        mFadeOutAnim.setFloatValues(0f, 1f);
647        mFadeOutAnim.removeAllUpdateListeners();
648        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
649            public void onAnimationUpdate(ValueAnimator animation) {
650                final float percent = (Float) animation.getAnimatedValue();
651
652                float alpha = 1 - percent;
653                mDropView.setAlpha(alpha);
654            }
655        });
656        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
657            public void onAnimationEnd(Animator animation) {
658                if (mDropView != null) {
659                    mDropView.remove();
660                }
661                mDropView = null;
662                invalidate();
663            }
664        });
665        mFadeOutAnim.start();
666    }
667
668    @Override
669    protected void onViewAdded(View child) {
670        super.onViewAdded(child);
671        updateChildIndices();
672    }
673
674    @Override
675    protected void onViewRemoved(View child) {
676        super.onViewRemoved(child);
677        updateChildIndices();
678    }
679
680    private void updateChildIndices() {
681        if (mLauncher != null) {
682            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
683            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
684        }
685    }
686
687    @Override
688    protected int getChildDrawingOrder(int childCount, int i) {
689        // We don't want to prioritize the workspace drawing on top of the other children in
690        // landscape for the overscroll event.
691        if (LauncherApplication.isScreenLandscape(getContext())) {
692            return super.getChildDrawingOrder(childCount, i);
693        }
694
695        if (mWorkspaceIndex == -1 || mQsbIndex == -1 ||
696                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
697            return i;
698        }
699
700        // This ensures that the workspace is drawn above the hotseat and qsb,
701        // except when the workspace is drawing a background gradient, in which
702        // case we want the workspace to stay behind these elements.
703        if (i == mQsbIndex) {
704            return mWorkspaceIndex;
705        } else if (i == mWorkspaceIndex) {
706            return mQsbIndex;
707        } else {
708            return i;
709        }
710    }
711}
712