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.launcher3;
18
19import android.animation.ValueAnimator;
20import android.animation.ValueAnimator.AnimatorUpdateListener;
21import android.content.res.Resources;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Point;
26import android.graphics.PorterDuff;
27import android.graphics.PorterDuffColorFilter;
28import android.graphics.Rect;
29import android.view.View;
30import android.view.animation.DecelerateInterpolator;
31
32public class DragView extends View {
33    private static float sDragAlpha = 1f;
34
35    private Bitmap mBitmap;
36    private Bitmap mCrossFadeBitmap;
37    private Paint mPaint;
38    private int mRegistrationX;
39    private int mRegistrationY;
40
41    private Point mDragVisualizeOffset = null;
42    private Rect mDragRegion = null;
43    private DragLayer mDragLayer = null;
44    private boolean mHasDrawn = false;
45    private float mCrossFadeProgress = 0f;
46
47    ValueAnimator mAnim;
48    private float mOffsetX = 0.0f;
49    private float mOffsetY = 0.0f;
50    private float mInitialScale = 1f;
51    // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
52    // size.  This is ignored for non-icons.
53    private float mIntrinsicIconScale = 1f;
54
55    /**
56     * Construct the drag view.
57     * <p>
58     * The registration point is the point inside our view that the touch events should
59     * be centered upon.
60     *
61     * @param launcher The Launcher instance
62     * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
63     * @param registrationX The x coordinate of the registration point.
64     * @param registrationY The y coordinate of the registration point.
65     */
66    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
67            int left, int top, int width, int height, final float initialScale) {
68        super(launcher);
69        mDragLayer = launcher.getDragLayer();
70        mInitialScale = initialScale;
71
72        final Resources res = getResources();
73        final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX);
74        final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY);
75        final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
76        final float scale = (width + scaleDps) / width;
77
78        // Set the initial scale to avoid any jumps
79        setScaleX(initialScale);
80        setScaleY(initialScale);
81
82        // Animate the view into the correct position
83        mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
84        mAnim.setDuration(150);
85        mAnim.addUpdateListener(new AnimatorUpdateListener() {
86            @Override
87            public void onAnimationUpdate(ValueAnimator animation) {
88                final float value = (Float) animation.getAnimatedValue();
89
90                final int deltaX = (int) ((value * offsetX) - mOffsetX);
91                final int deltaY = (int) ((value * offsetY) - mOffsetY);
92
93                mOffsetX += deltaX;
94                mOffsetY += deltaY;
95                setScaleX(initialScale + (value * (scale - initialScale)));
96                setScaleY(initialScale + (value * (scale - initialScale)));
97                if (sDragAlpha != 1f) {
98                    setAlpha(sDragAlpha * value + (1f - value));
99                }
100
101                if (getParent() == null) {
102                    animation.cancel();
103                } else {
104                    setTranslationX(getTranslationX() + deltaX);
105                    setTranslationY(getTranslationY() + deltaY);
106                }
107            }
108        });
109
110        mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
111        setDragRegion(new Rect(0, 0, width, height));
112
113        // The point in our scaled bitmap that the touch events are located
114        mRegistrationX = registrationX;
115        mRegistrationY = registrationY;
116
117        // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
118        int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
119        measure(ms, ms);
120        mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
121    }
122
123    /** Sets the scale of the view over the normal workspace icon size. */
124    public void setIntrinsicIconScaleFactor(float scale) {
125        mIntrinsicIconScale = scale;
126    }
127
128    public float getIntrinsicIconScaleFactor() {
129        return mIntrinsicIconScale;
130    }
131
132    public float getOffsetY() {
133        return mOffsetY;
134    }
135
136    public int getDragRegionLeft() {
137        return mDragRegion.left;
138    }
139
140    public int getDragRegionTop() {
141        return mDragRegion.top;
142    }
143
144    public int getDragRegionWidth() {
145        return mDragRegion.width();
146    }
147
148    public int getDragRegionHeight() {
149        return mDragRegion.height();
150    }
151
152    public void setDragVisualizeOffset(Point p) {
153        mDragVisualizeOffset = p;
154    }
155
156    public Point getDragVisualizeOffset() {
157        return mDragVisualizeOffset;
158    }
159
160    public void setDragRegion(Rect r) {
161        mDragRegion = r;
162    }
163
164    public Rect getDragRegion() {
165        return mDragRegion;
166    }
167
168    public float getInitialScale() {
169        return mInitialScale;
170    }
171
172    public void updateInitialScaleToCurrentScale() {
173        mInitialScale = getScaleX();
174    }
175
176    @Override
177    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
178        setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
179    }
180
181    @Override
182    protected void onDraw(Canvas canvas) {
183        @SuppressWarnings("all") // suppress dead code warning
184        final boolean debug = false;
185        if (debug) {
186            Paint p = new Paint();
187            p.setStyle(Paint.Style.FILL);
188            p.setColor(0x66ffffff);
189            canvas.drawRect(0, 0, getWidth(), getHeight(), p);
190        }
191
192        mHasDrawn = true;
193        boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
194        if (crossFade) {
195            int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
196            mPaint.setAlpha(alpha);
197        }
198        canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
199        if (crossFade) {
200            mPaint.setAlpha((int) (255 * mCrossFadeProgress));
201            canvas.save();
202            float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
203            float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
204            canvas.scale(sX, sY);
205            canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
206            canvas.restore();
207        }
208    }
209
210    public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
211        mCrossFadeBitmap = crossFadeBitmap;
212    }
213
214    public void crossFade(int duration) {
215        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
216        va.setDuration(duration);
217        va.setInterpolator(new DecelerateInterpolator(1.5f));
218        va.addUpdateListener(new AnimatorUpdateListener() {
219            @Override
220            public void onAnimationUpdate(ValueAnimator animation) {
221                mCrossFadeProgress = animation.getAnimatedFraction();
222            }
223        });
224        va.start();
225    }
226
227    public void setColor(int color) {
228        if (mPaint == null) {
229            mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
230        }
231        if (color != 0) {
232            mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
233        } else {
234            mPaint.setColorFilter(null);
235        }
236        invalidate();
237    }
238
239    public boolean hasDrawn() {
240        return mHasDrawn;
241    }
242
243    @Override
244    public void setAlpha(float alpha) {
245        super.setAlpha(alpha);
246        mPaint.setAlpha((int) (255 * alpha));
247        invalidate();
248    }
249
250    /**
251     * Create a window containing this view and show it.
252     *
253     * @param windowToken obtained from v.getWindowToken() from one of your views
254     * @param touchX the x coordinate the user touched in DragLayer coordinates
255     * @param touchY the y coordinate the user touched in DragLayer coordinates
256     */
257    public void show(int touchX, int touchY) {
258        mDragLayer.addView(this);
259
260        // Start the pick-up animation
261        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
262        lp.width = mBitmap.getWidth();
263        lp.height = mBitmap.getHeight();
264        lp.customPosition = true;
265        setLayoutParams(lp);
266        setTranslationX(touchX - mRegistrationX);
267        setTranslationY(touchY - mRegistrationY);
268        // Post the animation to skip other expensive work happening on the first frame
269        post(new Runnable() {
270                public void run() {
271                    mAnim.start();
272                }
273            });
274    }
275
276    public void cancelAnimation() {
277        if (mAnim != null && mAnim.isRunning()) {
278            mAnim.cancel();
279        }
280    }
281
282    public void resetLayoutParams() {
283        mOffsetX = mOffsetY = 0;
284        requestLayout();
285    }
286
287    /**
288     * Move the window containing this view.
289     *
290     * @param touchX the x coordinate the user touched in DragLayer coordinates
291     * @param touchY the y coordinate the user touched in DragLayer coordinates
292     */
293    void move(int touchX, int touchY) {
294        setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
295        setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
296    }
297
298    void remove() {
299        if (getParent() != null) {
300            mDragLayer.removeView(DragView.this);
301        }
302    }
303}
304
305