DragLayer.java revision 360e63fd3e77247002b86da2a77bd8dfe8c8a807
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            float childrenScale = parent.getChildrenScale();
467            TextView tv = (TextView) child;
468
469            // The child may be scaled (always about the center of the view) so to account for it,
470            // we have to offset the position by the scaled size.  Once we do that, we can center
471            // the drag view about the scaled child view.
472            toY += Math.round(((1f - childrenScale) * child.getMeasuredHeight()) / 2 +
473                    scale * childrenScale * tv.getPaddingTop());
474            toY -= dragView.getMeasuredHeight() * (1 - scale * childrenScale) / 2;
475            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
476
477            scale *= childrenScale;
478        } else if (child instanceof FolderIcon) {
479            // Account for holographic blur padding on the drag view
480            toY -= Workspace.DRAG_BITMAP_PADDING / 2;
481            // Center in the x coordinate about the target's drawable
482            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
483        } else {
484            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
485            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
486                    - child.getMeasuredWidth()))) / 2;
487        }
488
489        final int fromX = r.left;
490        final int fromY = r.top;
491        child.setVisibility(INVISIBLE);
492        Runnable onCompleteRunnable = new Runnable() {
493            public void run() {
494                child.setVisibility(VISIBLE);
495                if (onFinishAnimationRunnable != null) {
496                    onFinishAnimationRunnable.run();
497                }
498            }
499        };
500        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
501                onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
502    }
503
504    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
505            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
506            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
507            int animationEndStyle, int duration, View anchorView) {
508        Rect from = new Rect(fromX, fromY, fromX +
509                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
510        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
511        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
512                null, null, onCompleteRunnable, animationEndStyle, anchorView);
513    }
514
515    /**
516     * This method animates a view at the end of a drag and drop animation.
517     *
518     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
519     *        doesn't need to be a child of DragLayer.
520     * @param from The initial location of the view. Only the left and top parameters are used.
521     * @param to The final location of the view. Only the left and top parameters are used. This
522     *        location doesn't account for scaling, and so should be centered about the desired
523     *        final location (including scaling).
524     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
525     * @param finalScale The final scale of the view. The view is scaled about its center.
526     * @param duration The duration of the animation.
527     * @param motionInterpolator The interpolator to use for the location of the view.
528     * @param alphaInterpolator The interpolator to use for the alpha of the view.
529     * @param onCompleteRunnable Optional runnable to run on animation completion.
530     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
531     *        the runnable will execute after the view is faded out.
532     * @param anchorView If not null, this represents the view which the animated view stays
533     *        anchored to in case scrolling is currently taking place. Note: currently this is
534     *        only used for the X dimension for the case of the workspace.
535     */
536    public void animateView(final DragView view, final Rect from, final Rect to,
537            final float finalAlpha, final float initScaleX, final float initScaleY,
538            final float finalScaleX, final float finalScaleY, int duration,
539            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
540            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
541
542        // Calculate the duration of the animation based on the object's distance
543        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
544                Math.pow(to.top - from.top, 2));
545        final Resources res = getResources();
546        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
547
548        // If duration < 0, this is a cue to compute the duration based on the distance
549        if (duration < 0) {
550            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
551            if (dist < maxDist) {
552                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
553            }
554            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
555        }
556
557        // Fall back to cubic ease out interpolator for the animation if none is specified
558        TimeInterpolator interpolator = null;
559        if (alphaInterpolator == null || motionInterpolator == null) {
560            interpolator = mCubicEaseOutInterpolator;
561        }
562
563        // Animate the view
564        final float initAlpha = view.getAlpha();
565        final float dropViewScale = view.getScaleX();
566        AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
567            @Override
568            public void onAnimationUpdate(ValueAnimator animation) {
569                final float percent = (Float) animation.getAnimatedValue();
570                final int width = view.getMeasuredWidth();
571                final int height = view.getMeasuredHeight();
572
573                float alphaPercent = alphaInterpolator == null ? percent :
574                        alphaInterpolator.getInterpolation(percent);
575                float motionPercent = motionInterpolator == null ? percent :
576                        motionInterpolator.getInterpolation(percent);
577
578                float initialScaleX = initScaleX * dropViewScale;
579                float initialScaleY = initScaleY * dropViewScale;
580                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
581                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
582                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
583
584                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
585                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
586
587                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
588                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
589
590                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
591                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
592                int yPos = y - mDropView.getScrollY();
593
594                mDropView.setTranslationX(xPos);
595                mDropView.setTranslationY(yPos);
596                mDropView.setScaleX(scaleX);
597                mDropView.setScaleY(scaleY);
598                mDropView.setAlpha(alpha);
599            }
600        };
601        animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
602                anchorView);
603    }
604
605    public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
606            TimeInterpolator interpolator, final Runnable onCompleteRunnable,
607            final int animationEndStyle, View anchorView) {
608        // Clean up the previous animations
609        if (mDropAnim != null) mDropAnim.cancel();
610        if (mFadeOutAnim != null) mFadeOutAnim.cancel();
611
612        // Show the drop view if it was previously hidden
613        mDropView = view;
614        mDropView.cancelAnimation();
615        mDropView.resetLayoutParams();
616
617        // Set the anchor view if the page is scrolling
618        if (anchorView != null) {
619            mAnchorViewInitialScrollX = anchorView.getScrollX();
620        }
621        mAnchorView = anchorView;
622
623        // Create and start the animation
624        mDropAnim = new ValueAnimator();
625        mDropAnim.setInterpolator(interpolator);
626        mDropAnim.setDuration(duration);
627        mDropAnim.setFloatValues(0f, 1f);
628        mDropAnim.addUpdateListener(updateCb);
629        mDropAnim.addListener(new AnimatorListenerAdapter() {
630            public void onAnimationEnd(Animator animation) {
631                if (onCompleteRunnable != null) {
632                    onCompleteRunnable.run();
633                }
634                switch (animationEndStyle) {
635                case ANIMATION_END_DISAPPEAR:
636                    clearAnimatedView();
637                    break;
638                case ANIMATION_END_FADE_OUT:
639                    fadeOutDragView();
640                    break;
641                case ANIMATION_END_REMAIN_VISIBLE:
642                    break;
643                }
644            }
645        });
646        mDropAnim.start();
647    }
648
649    public void clearAnimatedView() {
650        if (mDropAnim != null) {
651            mDropAnim.cancel();
652        }
653        if (mDropView != null) {
654            mDragController.onDeferredEndDrag(mDropView);
655        }
656        mDropView = null;
657        invalidate();
658    }
659
660    public View getAnimatedView() {
661        return mDropView;
662    }
663
664    private void fadeOutDragView() {
665        mFadeOutAnim = new ValueAnimator();
666        mFadeOutAnim.setDuration(150);
667        mFadeOutAnim.setFloatValues(0f, 1f);
668        mFadeOutAnim.removeAllUpdateListeners();
669        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
670            public void onAnimationUpdate(ValueAnimator animation) {
671                final float percent = (Float) animation.getAnimatedValue();
672
673                float alpha = 1 - percent;
674                mDropView.setAlpha(alpha);
675            }
676        });
677        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
678            public void onAnimationEnd(Animator animation) {
679                if (mDropView != null) {
680                    mDragController.onDeferredEndDrag(mDropView);
681                }
682                mDropView = null;
683                invalidate();
684            }
685        });
686        mFadeOutAnim.start();
687    }
688
689    @Override
690    public void onChildViewAdded(View parent, View child) {
691        updateChildIndices();
692    }
693
694    @Override
695    public void onChildViewRemoved(View parent, View child) {
696        updateChildIndices();
697    }
698
699    private void updateChildIndices() {
700        if (mLauncher != null) {
701            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
702            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
703        }
704    }
705
706    @Override
707    protected int getChildDrawingOrder(int childCount, int i) {
708        // We don't want to prioritize the workspace drawing on top of the other children in
709        // landscape for the overscroll event.
710        if (LauncherApplication.isScreenLandscape(getContext())) {
711            return super.getChildDrawingOrder(childCount, i);
712        }
713
714        if (mWorkspaceIndex == -1 || mQsbIndex == -1 ||
715                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
716            return i;
717        }
718
719        // This ensures that the workspace is drawn above the hotseat and qsb,
720        // except when the workspace is drawing a background gradient, in which
721        // case we want the workspace to stay behind these elements.
722        if (i == mQsbIndex) {
723            return mWorkspaceIndex;
724        } else if (i == mWorkspaceIndex) {
725            return mQsbIndex;
726        } else {
727            return i;
728        }
729    }
730
731    private boolean mInScrollArea;
732    private Drawable mLeftHoverDrawable;
733    private Drawable mRightHoverDrawable;
734
735    void onEnterScrollArea(int direction) {
736        mInScrollArea = true;
737        invalidate();
738    }
739
740    void onExitScrollArea() {
741        mInScrollArea = false;
742        invalidate();
743    }
744
745    @Override
746    protected void dispatchDraw(Canvas canvas) {
747        super.dispatchDraw(canvas);
748
749        if (mInScrollArea && !LauncherApplication.isScreenLarge()) {
750            Workspace workspace = mLauncher.getWorkspace();
751            int width = workspace.getWidth();
752            Rect childRect = new Rect();
753            getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
754
755            int page = workspace.getNextPage();
756            CellLayout leftPage = (CellLayout) workspace.getChildAt(page - 1);
757            CellLayout rightPage = (CellLayout) workspace.getChildAt(page + 1);
758
759            if (leftPage != null && leftPage.getIsDragOverlapping()) {
760                mLeftHoverDrawable.setBounds(0, childRect.top,
761                        mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
762                mLeftHoverDrawable.draw(canvas);
763            } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
764                mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
765                        childRect.top, width, childRect.bottom);
766                mRightHoverDrawable.draw(canvas);
767            }
768        }
769    }
770}
771