DragLayer.java revision 8dfcba4af7a7ece09e8c7d96053e54f3a383e905
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.util.AttributeSet;
30import android.view.KeyEvent;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewParent;
34import android.view.animation.DecelerateInterpolator;
35import android.view.animation.Interpolator;
36import android.widget.FrameLayout;
37
38import com.android.launcher.R;
39
40import java.util.ArrayList;
41
42/**
43 * A ViewGroup that coordinates dragging across its descendants
44 */
45public class DragLayer extends FrameLayout {
46    private DragController mDragController;
47    private int[] mTmpXY = new int[2];
48
49    private int mXDown, mYDown;
50    private Launcher mLauncher;
51
52    // Variables relating to resizing widgets
53    private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
54            new ArrayList<AppWidgetResizeFrame>();
55    private AppWidgetResizeFrame mCurrentResizeFrame;
56
57    // Variables relating to animation of views after drop
58    private ValueAnimator mDropAnim = null;
59    private ValueAnimator mFadeOutAnim = null;
60    private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
61    private View mDropView = null;
62
63    private int[] mDropViewPos = new int[2];
64    private float mDropViewScale;
65    private float mDropViewAlpha;
66
67    /**
68     * Used to create a new DragLayer from XML.
69     *
70     * @param context The application's context.
71     * @param attrs The attributes set containing the Workspace's customization values.
72     */
73    public DragLayer(Context context, AttributeSet attrs) {
74        super(context, attrs);
75
76        // Disable multitouch across the workspace/all apps/customize tray
77        setMotionEventSplittingEnabled(false);
78    }
79
80    public void setup(Launcher launcher, DragController controller) {
81        mLauncher = launcher;
82        mDragController = controller;
83    }
84
85    @Override
86    public boolean dispatchKeyEvent(KeyEvent event) {
87        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
88    }
89
90    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
91        Rect hitRect = new Rect();
92        int x = (int) ev.getX();
93        int y = (int) ev.getY();
94
95        for (AppWidgetResizeFrame child: mResizeFrames) {
96            child.getHitRect(hitRect);
97            if (hitRect.contains(x, y)) {
98                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
99                    mCurrentResizeFrame = child;
100                    mXDown = x;
101                    mYDown = y;
102                    requestDisallowInterceptTouchEvent(true);
103                    return true;
104                }
105            }
106        }
107
108        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
109        if (currentFolder != null && intercept) {
110            if (currentFolder.isEditingName()) {
111                getDescendantRectRelativeToSelf(currentFolder.getEditTextRegion(), hitRect);
112                if (!hitRect.contains(x, y)) {
113                    currentFolder.dismissEditingName();
114                    return true;
115                }
116            }
117
118            getDescendantRectRelativeToSelf(currentFolder, hitRect);
119            if (!hitRect.contains(x, y)) {
120                mLauncher.closeFolder();
121                return true;
122            }
123        }
124        return false;
125    }
126
127    @Override
128    public boolean onInterceptTouchEvent(MotionEvent ev) {
129        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
130            if (handleTouchDown(ev, true)) {
131                return true;
132            }
133        }
134        clearAllResizeFrames();
135        return mDragController.onInterceptTouchEvent(ev);
136    }
137
138    @Override
139    public boolean onTouchEvent(MotionEvent ev) {
140        boolean handled = false;
141        int action = ev.getAction();
142
143        int x = (int) ev.getX();
144        int y = (int) ev.getY();
145
146        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
147            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
148                if (handleTouchDown(ev, false)) {
149                    return true;
150                }
151            }
152        }
153
154        if (mCurrentResizeFrame != null) {
155            handled = true;
156            switch (action) {
157                case MotionEvent.ACTION_MOVE:
158                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
159                    break;
160                case MotionEvent.ACTION_CANCEL:
161                case MotionEvent.ACTION_UP:
162                    mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown);
163                    mCurrentResizeFrame = null;
164            }
165        }
166        if (handled) return true;
167        return mDragController.onTouchEvent(ev);
168    }
169
170    public void getDescendantRectRelativeToSelf(View descendant, Rect r) {
171        mTmpXY[0] = 0;
172        mTmpXY[1] = 0;
173        getDescendantCoordRelativeToSelf(descendant, mTmpXY);
174        r.set(mTmpXY[0], mTmpXY[1],
175                mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
176    }
177
178    private void getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
179        coord[0] += descendant.getLeft();
180        coord[1] += descendant.getTop();
181        ViewParent viewParent = descendant.getParent();
182        while (viewParent instanceof View && viewParent != this) {
183            final View view = (View)viewParent;
184            coord[0] += view.getLeft() + (int) (view.getTranslationX() + 0.5f) - view.getScrollX();
185            coord[1] += view.getTop() + (int) (view.getTranslationY() + 0.5f) - view.getScrollY();
186            viewParent = view.getParent();
187        }
188    }
189
190    public void getLocationInDragLayer(View child, int[] loc) {
191        loc[0] = 0;
192        loc[1] = 0;
193        getDescendantCoordRelativeToSelf(child, loc);
194    }
195
196    public void getViewRectRelativeToSelf(View v, Rect r) {
197        int[] loc = new int[2];
198        getLocationInWindow(loc);
199        int x = loc[0];
200        int y = loc[1];
201
202        v.getLocationInWindow(loc);
203        int vX = loc[0];
204        int vY = loc[1];
205
206        int left = vX - x;
207        int top = vY - y;
208        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
209    }
210
211    @Override
212    public boolean dispatchUnhandledMove(View focused, int direction) {
213        return mDragController.dispatchUnhandledMove(focused, direction);
214    }
215
216    public static class LayoutParams extends FrameLayout.LayoutParams {
217        public int x, y;
218        public boolean customPosition = false;
219
220        /**
221         * {@inheritDoc}
222         */
223        public LayoutParams(int width, int height) {
224            super(width, height);
225        }
226
227        public void setWidth(int width) {
228            this.width = width;
229        }
230
231        public int getWidth() {
232            return width;
233        }
234
235        public void setHeight(int height) {
236            this.height = height;
237        }
238
239        public int getHeight() {
240            return height;
241        }
242
243        public void setX(int x) {
244            this.x = x;
245        }
246
247        public int getX() {
248            return x;
249        }
250
251        public void setY(int y) {
252            this.y = y;
253        }
254
255        public int getY() {
256            return y;
257        }
258    }
259
260    protected void onLayout(boolean changed, int l, int t, int r, int b) {
261        super.onLayout(changed, l, t, r, b);
262        int count = getChildCount();
263        for (int i = 0; i < count; i++) {
264            View child = getChildAt(i);
265            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
266            if (flp instanceof LayoutParams) {
267                final LayoutParams lp = (LayoutParams) flp;
268                if (lp.customPosition) {
269                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
270                }
271            }
272        }
273    }
274
275    public void clearAllResizeFrames() {
276        if (mResizeFrames.size() > 0) {
277            for (AppWidgetResizeFrame frame: mResizeFrames) {
278                removeView(frame);
279            }
280            mResizeFrames.clear();
281        }
282    }
283
284    public boolean hasResizeFrames() {
285        return mResizeFrames.size() > 0;
286    }
287
288    public boolean isWidgetBeingResized() {
289        return mCurrentResizeFrame != null;
290    }
291
292    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
293            CellLayout cellLayout) {
294        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
295                itemInfo, widget, cellLayout, this);
296
297        LayoutParams lp = new LayoutParams(-1, -1);
298        lp.customPosition = true;
299
300        addView(resizeFrame, lp);
301        mResizeFrames.add(resizeFrame);
302
303        resizeFrame.snapToWidget(false);
304    }
305
306    public void animateViewIntoPosition(DragView dragView, final View child) {
307        ((CellLayoutChildren) child.getParent()).measureChild(child);
308        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
309
310        Rect r = new Rect();
311        getViewRectRelativeToSelf(dragView, r);
312
313        int coord[] = new int[2];
314        coord[0] = lp.x;
315        coord[1] = lp.y;
316        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
317        // the correct coordinates and use these to determine the final location
318        getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
319        int toX = coord[0] - (dragView.getWidth() - child.getMeasuredWidth()) / 2;
320        int toY = coord[1] - (dragView.getHeight() - child.getMeasuredHeight()) / 2;
321
322        final int fromX = r.left + (dragView.getWidth() - child.getMeasuredWidth())  / 2;
323        final int fromY = r.top + (dragView.getHeight() - child.getMeasuredHeight())  / 2;
324        child.setVisibility(INVISIBLE);
325        child.setAlpha(0);
326        Runnable onCompleteRunnable = new Runnable() {
327            public void run() {
328                child.setVisibility(VISIBLE);
329                ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
330                oa.setDuration(60);
331                oa.start();
332            }
333        };
334        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, onCompleteRunnable, true);
335    }
336
337    private void animateViewIntoPosition(final View view, final int fromX, final int fromY,
338            final int toX, final int toY, Runnable onCompleteRunnable, boolean fadeOut) {
339        Rect from = new Rect(fromX, fromY, fromX +
340                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
341        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
342        animateView(view, from, to, 1f, 1.0f, -1, null, null, onCompleteRunnable, true);
343
344    }
345
346    public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha,
347            final float finalScale, int duration, final Interpolator motionInterpolator,
348            final Interpolator alphaInterpolator, final Runnable onCompleteRunnable,
349            final boolean fadeOut) {
350        // Calculate the duration of the animation based on the object's distance
351        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
352                Math.pow(to.top - from.top, 2));
353        final Resources res = getResources();
354        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
355
356        // If duration < 0, this is a cue to compute the duration based on the distance
357        if (duration < 0) {
358            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
359            if (dist < maxDist) {
360                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
361            }
362        }
363
364        if (mDropAnim != null) {
365            mDropAnim.cancel();
366        }
367
368        mDropView = view;
369        final float initialAlpha = view.getAlpha();
370        mDropAnim = new ValueAnimator();
371        if (alphaInterpolator == null || motionInterpolator == null) {
372            mDropAnim.setInterpolator(mCubicEaseOutInterpolator);
373        }
374
375        mDropAnim.setDuration(duration);
376        mDropAnim.setFloatValues(0.0f, 1.0f);
377        mDropAnim.removeAllUpdateListeners();
378        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
379            public void onAnimationUpdate(ValueAnimator animation) {
380                final float percent = (Float) animation.getAnimatedValue();
381                // Invalidate the old position
382                int width = view.getMeasuredWidth();
383                int height = view.getMeasuredHeight();
384                invalidate(mDropViewPos[0], mDropViewPos[1],
385                        mDropViewPos[0] + width, mDropViewPos[1] + height);
386
387                float alphaPercent = alphaInterpolator == null ? percent :
388                        alphaInterpolator.getInterpolation(percent);
389                float motionPercent = motionInterpolator == null ? percent :
390                        motionInterpolator.getInterpolation(percent);
391
392                mDropViewPos[0] = from.left + (int) ((to.left - from.left) * motionPercent);
393                mDropViewPos[1] = from.top + (int) ((to.top - from.top) * motionPercent);
394                mDropViewScale = percent * finalScale + (1 - percent);
395                mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha;
396                invalidate(mDropViewPos[0], mDropViewPos[1],
397                        mDropViewPos[0] + width, mDropViewPos[1] + height);
398            }
399        });
400        mDropAnim.addListener(new AnimatorListenerAdapter() {
401            public void onAnimationEnd(Animator animation) {
402                if (onCompleteRunnable != null) {
403                    onCompleteRunnable.run();
404                }
405                if (fadeOut) {
406                    fadeOutDragView();
407                } else {
408                    mDropView = null;
409                }
410            }
411        });
412        mDropAnim.start();
413    }
414
415    private void fadeOutDragView() {
416        mFadeOutAnim = new ValueAnimator();
417        mFadeOutAnim.setDuration(150);
418        mFadeOutAnim.setFloatValues(0f, 1f);
419        mFadeOutAnim.removeAllUpdateListeners();
420        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
421            public void onAnimationUpdate(ValueAnimator animation) {
422                final float percent = (Float) animation.getAnimatedValue();
423                mDropViewAlpha = 1 - percent;
424                int width = mDropView.getMeasuredWidth();
425                int height = mDropView.getMeasuredHeight();
426                invalidate(mDropViewPos[0], mDropViewPos[1],
427                        mDropViewPos[0] + width, mDropViewPos[1] + height);
428            }
429        });
430        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
431            public void onAnimationEnd(Animator animation) {
432                mDropView = null;
433            }
434        });
435        mFadeOutAnim.start();
436    }
437
438    @Override
439    protected void dispatchDraw(Canvas canvas) {
440        super.dispatchDraw(canvas);
441        if (mDropView != null) {
442            // We are animating an item that was just dropped on the home screen.
443            // Render its View in the current animation position.
444            canvas.save(Canvas.MATRIX_SAVE_FLAG);
445            final int xPos = mDropViewPos[0] - mDropView.getScrollX();
446            final int yPos = mDropViewPos[1] - mDropView.getScrollY();
447            int width = mDropView.getMeasuredWidth();
448            int height = mDropView.getMeasuredHeight();
449            canvas.translate(xPos, yPos);
450            canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2);
451            canvas.scale(mDropViewScale, mDropViewScale);
452            mDropView.setAlpha(mDropViewAlpha);
453            mDropView.draw(canvas);
454            canvas.restore();
455        }
456    }
457}
458