DragLayer.java revision 307fe23f125cbbd5512ad8d4660025f2ab68f30b
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.Rect;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.KeyEvent;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
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 implements ViewGroup.OnHierarchyChangeListener {
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        setOnHierarchyChangeListener(this);
90
91        mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
92        mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
93    }
94
95    public void setup(Launcher launcher, DragController controller) {
96        mLauncher = launcher;
97        mDragController = controller;
98    }
99
100    @Override
101    public boolean dispatchKeyEvent(KeyEvent event) {
102        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
103    }
104
105    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
106        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
107        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
108            return true;
109        }
110        return false;
111    }
112
113    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
114        getDescendantRectRelativeToSelf(folder, mHitRect);
115        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
116            return true;
117        }
118        return false;
119    }
120
121    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
122        Rect hitRect = new Rect();
123        int x = (int) ev.getX();
124        int y = (int) ev.getY();
125
126        for (AppWidgetResizeFrame child: mResizeFrames) {
127            child.getHitRect(hitRect);
128            if (hitRect.contains(x, y)) {
129                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
130                    mCurrentResizeFrame = child;
131                    mXDown = x;
132                    mYDown = y;
133                    requestDisallowInterceptTouchEvent(true);
134                    return true;
135                }
136            }
137        }
138
139        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
140        if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
141            if (currentFolder.isEditingName()) {
142                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
143                    currentFolder.dismissEditingName();
144                    return true;
145                }
146            }
147
148            getDescendantRectRelativeToSelf(currentFolder, hitRect);
149            if (!isEventOverFolder(currentFolder, ev)) {
150                mLauncher.closeFolder();
151                return true;
152            }
153        }
154        return false;
155    }
156
157    @Override
158    public boolean onInterceptTouchEvent(MotionEvent ev) {
159        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
160            if (handleTouchDown(ev, true)) {
161                return true;
162            }
163        }
164        clearAllResizeFrames();
165        return mDragController.onInterceptTouchEvent(ev);
166    }
167
168    @Override
169    public boolean onInterceptHoverEvent(MotionEvent ev) {
170        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
171        if (currentFolder == null) {
172            return false;
173        } else {
174                AccessibilityManager accessibilityManager = (AccessibilityManager)
175                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
176            if (accessibilityManager.isTouchExplorationEnabled()) {
177                final int action = ev.getAction();
178                boolean isOverFolder;
179                switch (action) {
180                    case MotionEvent.ACTION_HOVER_ENTER:
181                        isOverFolder = isEventOverFolder(currentFolder, ev);
182                        if (!isOverFolder) {
183                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
184                            mHoverPointClosesFolder = true;
185                            return true;
186                        } else if (isOverFolder) {
187                            mHoverPointClosesFolder = false;
188                        } else {
189                            return true;
190                        }
191                    case MotionEvent.ACTION_HOVER_MOVE:
192                        isOverFolder = isEventOverFolder(currentFolder, ev);
193                        if (!isOverFolder && !mHoverPointClosesFolder) {
194                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
195                            mHoverPointClosesFolder = true;
196                            return true;
197                        } else if (isOverFolder) {
198                            mHoverPointClosesFolder = false;
199                        } else {
200                            return true;
201                        }
202                }
203            }
204        }
205        return false;
206    }
207
208    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
209        AccessibilityManager accessibilityManager = (AccessibilityManager)
210                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
211        if (accessibilityManager.isEnabled()) {
212            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
213            AccessibilityEvent event = AccessibilityEvent.obtain(
214                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
215            onInitializeAccessibilityEvent(event);
216            event.getText().add(getContext().getString(stringId));
217            accessibilityManager.sendAccessibilityEvent(event);
218        }
219    }
220
221    @Override
222    public boolean onHoverEvent(MotionEvent ev) {
223        // If we've received this, we've already done the necessary handling
224        // in onInterceptHoverEvent. Return true to consume the event.
225        return false;
226    }
227
228    @Override
229    public boolean onTouchEvent(MotionEvent ev) {
230        boolean handled = false;
231        int action = ev.getAction();
232
233        int x = (int) ev.getX();
234        int y = (int) ev.getY();
235
236        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
237            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
238                if (handleTouchDown(ev, false)) {
239                    return true;
240                }
241            }
242        }
243
244        if (mCurrentResizeFrame != null) {
245            handled = true;
246            switch (action) {
247                case MotionEvent.ACTION_MOVE:
248                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
249                    break;
250                case MotionEvent.ACTION_CANCEL:
251                case MotionEvent.ACTION_UP:
252                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
253                    mCurrentResizeFrame.onTouchUp();
254                    mCurrentResizeFrame = null;
255            }
256        }
257        if (handled) return true;
258        return mDragController.onTouchEvent(ev);
259    }
260
261    /**
262     * Determine the rect of the descendant in this DragLayer's coordinates
263     *
264     * @param descendant The descendant whose coordinates we want to find.
265     * @param r The rect into which to place the results.
266     * @return The factor by which this descendant is scaled relative to this DragLayer.
267     */
268    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
269        mTmpXY[0] = 0;
270        mTmpXY[1] = 0;
271        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
272        r.set(mTmpXY[0], mTmpXY[1],
273                mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
274        return scale;
275    }
276
277    public float getLocationInDragLayer(View child, int[] loc) {
278        loc[0] = 0;
279        loc[1] = 0;
280        return getDescendantCoordRelativeToSelf(child, loc);
281    }
282
283    /**
284     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
285     * coordinates.
286     *
287     * @param descendant The descendant to which the passed coordinate is relative.
288     * @param coord The coordinate that we want mapped.
289     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
290     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
291     *         assumption fails, we will need to return a pair of scale factors.
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.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
450        parentChildren.measureChild(child);
451
452        Rect r = new Rect();
453        getViewRectRelativeToSelf(dragView, r);
454
455        int coord[] = new int[2];
456        float childScale = child.getScaleX();
457        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
458        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
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        // We need to account for the scale of the child itself, as the above only accounts for
464        // for the scale in parents.
465        scale *= childScale;
466        int toX = coord[0];
467        int toY = coord[1];
468        if (child instanceof TextView) {
469            TextView tv = (TextView) child;
470
471            // The child may be scaled (always about the center of the view) so to account for it,
472            // we have to offset the position by the scaled size.  Once we do that, we can center
473            // the drag view about the scaled child view.
474            toY += Math.round(scale * tv.getPaddingTop());
475            toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
476            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
477        } else if (child instanceof FolderIcon) {
478            // Account for holographic blur padding on the drag view
479            toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
480            toY -= (1 - scale) * dragView.getMeasuredHeight() / 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