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 
17 package com.android.launcher3;
18 
19 import android.animation.ValueAnimator;
20 import android.animation.ValueAnimator.AnimatorUpdateListener;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Paint;
25 import android.graphics.Point;
26 import android.graphics.PorterDuff;
27 import android.graphics.PorterDuffColorFilter;
28 import android.graphics.Rect;
29 import android.view.View;
30 import android.view.animation.DecelerateInterpolator;
31 
32 public 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      */
DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, int left, int top, int width, int height, final float initialScale)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. */
setIntrinsicIconScaleFactor(float scale)124     public void setIntrinsicIconScaleFactor(float scale) {
125         mIntrinsicIconScale = scale;
126     }
127 
getIntrinsicIconScaleFactor()128     public float getIntrinsicIconScaleFactor() {
129         return mIntrinsicIconScale;
130     }
131 
getOffsetY()132     public float getOffsetY() {
133         return mOffsetY;
134     }
135 
getDragRegionLeft()136     public int getDragRegionLeft() {
137         return mDragRegion.left;
138     }
139 
getDragRegionTop()140     public int getDragRegionTop() {
141         return mDragRegion.top;
142     }
143 
getDragRegionWidth()144     public int getDragRegionWidth() {
145         return mDragRegion.width();
146     }
147 
getDragRegionHeight()148     public int getDragRegionHeight() {
149         return mDragRegion.height();
150     }
151 
setDragVisualizeOffset(Point p)152     public void setDragVisualizeOffset(Point p) {
153         mDragVisualizeOffset = p;
154     }
155 
getDragVisualizeOffset()156     public Point getDragVisualizeOffset() {
157         return mDragVisualizeOffset;
158     }
159 
setDragRegion(Rect r)160     public void setDragRegion(Rect r) {
161         mDragRegion = r;
162     }
163 
getDragRegion()164     public Rect getDragRegion() {
165         return mDragRegion;
166     }
167 
getInitialScale()168     public float getInitialScale() {
169         return mInitialScale;
170     }
171 
updateInitialScaleToCurrentScale()172     public void updateInitialScaleToCurrentScale() {
173         mInitialScale = getScaleX();
174     }
175 
176     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)177     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
178         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
179     }
180 
181     @Override
onDraw(Canvas canvas)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 
setCrossFadeBitmap(Bitmap crossFadeBitmap)210     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
211         mCrossFadeBitmap = crossFadeBitmap;
212     }
213 
crossFade(int duration)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 
setColor(int color)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 
hasDrawn()239     public boolean hasDrawn() {
240         return mHasDrawn;
241     }
242 
243     @Override
setAlpha(float alpha)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      */
show(int touchX, int touchY)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 
cancelAnimation()276     public void cancelAnimation() {
277         if (mAnim != null && mAnim.isRunning()) {
278             mAnim.cancel();
279         }
280     }
281 
resetLayoutParams()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      */
move(int touchX, int touchY)293     void move(int touchX, int touchY) {
294         setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
295         setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
296     }
297 
remove()298     void remove() {
299         if (getParent() != null) {
300             mDragLayer.removeView(DragView.this);
301         }
302     }
303 }
304 
305