DragLayer.java revision 716b51e030f9c6ed34af2b947760e46a280c65a6
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.Bitmap;
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.widget.FrameLayout;
36import android.widget.ImageView;
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 TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f);
60    private int[] mDropViewPos = new int[] { -1, -1 };
61    private View mDropView = null;
62
63    /**
64     * Used to create a new DragLayer from XML.
65     *
66     * @param context The application's context.
67     * @param attrs The attributes set containing the Workspace's customization values.
68     */
69    public DragLayer(Context context, AttributeSet attrs) {
70        super(context, attrs);
71
72        // Disable multitouch across the workspace/all apps/customize tray
73        setMotionEventSplittingEnabled(false);
74    }
75
76    public void setup(Launcher launcher, DragController controller) {
77        mLauncher = launcher;
78        mDragController = controller;
79    }
80
81    @Override
82    public boolean dispatchKeyEvent(KeyEvent event) {
83        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
84    }
85
86    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
87        Rect hitRect = new Rect();
88        int x = (int) ev.getX();
89        int y = (int) ev.getY();
90
91        for (AppWidgetResizeFrame child: mResizeFrames) {
92            child.getHitRect(hitRect);
93            if (hitRect.contains(x, y)) {
94                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
95                    mCurrentResizeFrame = child;
96                    mXDown = x;
97                    mYDown = y;
98                    requestDisallowInterceptTouchEvent(true);
99                    return true;
100                }
101            }
102        }
103
104        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
105        if (currentFolder != null && intercept) {
106            if (currentFolder.isEditingName()) {
107                getDescendantRectRelativeToSelf(currentFolder.getEditTextRegion(), hitRect);
108                if (!hitRect.contains(x, y)) {
109                    currentFolder.dismissEditingName();
110                    return true;
111                }
112            }
113
114            getDescendantRectRelativeToSelf(currentFolder, hitRect);
115            if (!hitRect.contains(x, y)) {
116                mLauncher.closeFolder();
117                return true;
118            }
119        }
120        return false;
121    }
122
123    @Override
124    public boolean onInterceptTouchEvent(MotionEvent ev) {
125        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
126            if (handleTouchDown(ev, true)) {
127                return true;
128            }
129        }
130        clearAllResizeFrames();
131        return mDragController.onInterceptTouchEvent(ev);
132    }
133
134    @Override
135    public boolean onTouchEvent(MotionEvent ev) {
136        boolean handled = false;
137        int action = ev.getAction();
138
139        int x = (int) ev.getX();
140        int y = (int) ev.getY();
141
142        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
143            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
144                if (handleTouchDown(ev, false)) {
145                    return true;
146                }
147            }
148        }
149
150        if (mCurrentResizeFrame != null) {
151            handled = true;
152            switch (action) {
153                case MotionEvent.ACTION_MOVE:
154                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
155                    break;
156                case MotionEvent.ACTION_CANCEL:
157                case MotionEvent.ACTION_UP:
158                    mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown);
159                    mCurrentResizeFrame = null;
160            }
161        }
162        if (handled) return true;
163        return mDragController.onTouchEvent(ev);
164    }
165
166    public void getDescendantRectRelativeToSelf(View descendant, Rect r) {
167        descendant.getHitRect(r);
168        mTmpXY[0] = 0;
169        mTmpXY[1] = 0;
170        getDescendantCoordRelativeToSelf(descendant, mTmpXY);
171        r.offset(mTmpXY[0], mTmpXY[1]);
172    }
173
174    public void getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
175        ViewParent viewParent = descendant.getParent();
176        while (viewParent instanceof View && viewParent != this) {
177            final View view = (View)viewParent;
178            coord[0] += view.getLeft() + (int) (view.getTranslationX() + 0.5f) - view.getScrollX();
179            coord[1] += view.getTop() + (int) (view.getTranslationY() + 0.5f) - view.getScrollY();
180            viewParent = view.getParent();
181        }
182    }
183
184    public void getViewLocationRelativeToSelf(View v, int[] location) {
185        getLocationOnScreen(location);
186        int x = location[0];
187        int y = location[1];
188
189        v.getLocationOnScreen(location);
190        int vX = location[0];
191        int vY = location[1];
192
193        location[0] = vX - x;
194        location[1] = vY - y;
195    }
196
197    @Override
198    public boolean dispatchUnhandledMove(View focused, int direction) {
199        return mDragController.dispatchUnhandledMove(focused, direction);
200    }
201
202    public View createDragView(Bitmap b, int xPos, int yPos) {
203        ImageView imageView = new ImageView(mContext);
204        imageView.setImageBitmap(b);
205        imageView.setX(xPos);
206        imageView.setY(yPos);
207        addView(imageView, b.getWidth(), b.getHeight());
208
209        return imageView;
210    }
211
212    public View createDragView(View v) {
213        v.getLocationOnScreen(mTmpXY);
214        return createDragView(mDragController.getViewBitmap(v), mTmpXY[0], mTmpXY[1]);
215    }
216
217    public static class LayoutParams extends FrameLayout.LayoutParams {
218        public int x, y;
219        public boolean customPosition = false;
220
221        /**
222         * {@inheritDoc}
223         */
224        public LayoutParams(int width, int height) {
225            super(width, height);
226        }
227
228        public void setWidth(int width) {
229            this.width = width;
230        }
231
232        public int getWidth() {
233            return width;
234        }
235
236        public void setHeight(int height) {
237            this.height = height;
238        }
239
240        public int getHeight() {
241            return height;
242        }
243
244        public void setX(int x) {
245            this.x = x;
246        }
247
248        public int getX() {
249            return x;
250        }
251
252        public void setY(int y) {
253            this.y = y;
254        }
255
256        public int getY() {
257            return y;
258        }
259    }
260
261    protected void onLayout(boolean changed, int l, int t, int r, int b) {
262        super.onLayout(changed, l, t, r, b);
263        int count = getChildCount();
264        for (int i = 0; i < count; i++) {
265            View child = getChildAt(i);
266            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
267            if (flp instanceof LayoutParams) {
268                final LayoutParams lp = (LayoutParams) flp;
269                if (lp.customPosition) {
270                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
271                }
272            }
273        }
274    }
275
276    public void clearAllResizeFrames() {
277        if (mResizeFrames.size() > 0) {
278            for (AppWidgetResizeFrame frame: mResizeFrames) {
279                removeView(frame);
280            }
281            mResizeFrames.clear();
282        }
283    }
284
285    public boolean hasResizeFrames() {
286        return mResizeFrames.size() > 0;
287    }
288
289    public boolean isWidgetBeingResized() {
290        return mCurrentResizeFrame != null;
291    }
292
293    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
294            CellLayout cellLayout) {
295        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
296                itemInfo, widget, cellLayout, this);
297
298        LayoutParams lp = new LayoutParams(-1, -1);
299        lp.customPosition = true;
300
301        addView(resizeFrame, lp);
302        mResizeFrames.add(resizeFrame);
303
304        resizeFrame.snapToWidget(false);
305    }
306
307    public void animateViewIntoPosition(DragView dragView, final View child) {
308        ((CellLayoutChildren) child.getParent()).measureChild(child);
309        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
310
311        int[] loc = new int[2];
312        getViewLocationRelativeToSelf(dragView, loc);
313
314        int coord[] = new int[2];
315        coord[0] = lp.x;
316        coord[1] = lp.y;
317        getDescendantCoordRelativeToSelf(child, coord);
318
319        final int fromX = loc[0] + (dragView.getWidth() - child.getMeasuredWidth())  / 2;
320        final int fromY = loc[1] + (dragView.getHeight() - child.getMeasuredHeight())  / 2;
321        final int dx = coord[0] - fromX;
322        final int dy = coord[1] - fromY;
323
324        child.setVisibility(INVISIBLE);
325        animateViewIntoPosition(child, fromX, fromY, dx, dy);
326    }
327
328    private void animateViewIntoPosition(final View view, final int fromX, final int fromY,
329            final int dX, final int dY) {
330
331        // Calculate the duration of the animation based on the object's distance
332        final float dist = (float) Math.sqrt(dX*dX + dY*dY);
333        final Resources res = getResources();
334        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
335        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
336        if (dist < maxDist) {
337            duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist);
338        }
339
340        if (mDropAnim != null) {
341            mDropAnim.end();
342        }
343        mDropAnim = new ValueAnimator();
344        mDropAnim.setInterpolator(mQuintEaseOutInterpolator);
345
346        // The view is invisible during the animation; we render it manually.
347        mDropAnim.addListener(new AnimatorListenerAdapter() {
348            public void onAnimationStart(Animator animation) {
349                // Set this here so that we don't render it until the animation begins
350                mDropView = view;
351            }
352
353            public void onAnimationEnd(Animator animation) {
354                if (mDropView != null) {
355                    mDropView.setVisibility(View.VISIBLE);
356                    mDropView = null;
357                }
358            }
359        });
360
361        mDropAnim.setDuration(duration);
362        mDropAnim.setFloatValues(0.0f, 1.0f);
363        mDropAnim.removeAllUpdateListeners();
364        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
365            public void onAnimationUpdate(ValueAnimator animation) {
366                final float percent = (Float) animation.getAnimatedValue();
367                // Invalidate the old position
368                int width = view.getMeasuredWidth();
369                int height = view.getMeasuredHeight();
370                invalidate(mDropViewPos[0], mDropViewPos[1],
371                        mDropViewPos[0] + width, mDropViewPos[1] + height);
372
373                mDropViewPos[0] = fromX + (int) (percent * dX + 0.5f);
374                mDropViewPos[1] = fromY + (int) (percent * dY + 0.5f);
375                invalidate(mDropViewPos[0], mDropViewPos[1],
376                        mDropViewPos[0] + width, mDropViewPos[1] + height);
377            }
378        });
379        mDropAnim.start();
380    }
381
382    @Override
383    protected void dispatchDraw(Canvas canvas) {
384        super.dispatchDraw(canvas);
385        if (mDropView != null) {
386            // We are animating an item that was just dropped on the home screen.
387            // Render its View in the current animation position.
388            canvas.save(Canvas.MATRIX_SAVE_FLAG);
389            final int xPos = mDropViewPos[0] - mDropView.getScrollX();
390            final int yPos = mDropViewPos[1] - mDropView.getScrollY();
391            canvas.translate(xPos, yPos);
392            mDropView.draw(canvas);
393            canvas.restore();
394        }
395    }
396}
397