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