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 
18 package com.android.launcher2;
19 
20 import android.animation.ValueAnimator;
21 import android.animation.ValueAnimator.AnimatorUpdateListener;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.Point;
27 import android.graphics.PorterDuff;
28 import android.graphics.PorterDuffColorFilter;
29 import android.graphics.Rect;
30 import android.view.View;
31 import android.view.animation.DecelerateInterpolator;
32 
33 import com.android.launcher.R;
34 
35 public class DragView extends View {
36     private static float sDragAlpha = 1f;
37 
38     private Bitmap mBitmap;
39     private Bitmap mCrossFadeBitmap;
40     private Paint mPaint;
41     private int mRegistrationX;
42     private int mRegistrationY;
43 
44     private Point mDragVisualizeOffset = null;
45     private Rect mDragRegion = null;
46     private DragLayer mDragLayer = null;
47     private boolean mHasDrawn = false;
48     private float mCrossFadeProgress = 0f;
49 
50     ValueAnimator mAnim;
51     private float mOffsetX = 0.0f;
52     private float mOffsetY = 0.0f;
53     private float mInitialScale = 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 
getOffsetY()123     public float getOffsetY() {
124         return mOffsetY;
125     }
126 
getDragRegionLeft()127     public int getDragRegionLeft() {
128         return mDragRegion.left;
129     }
130 
getDragRegionTop()131     public int getDragRegionTop() {
132         return mDragRegion.top;
133     }
134 
getDragRegionWidth()135     public int getDragRegionWidth() {
136         return mDragRegion.width();
137     }
138 
getDragRegionHeight()139     public int getDragRegionHeight() {
140         return mDragRegion.height();
141     }
142 
setDragVisualizeOffset(Point p)143     public void setDragVisualizeOffset(Point p) {
144         mDragVisualizeOffset = p;
145     }
146 
getDragVisualizeOffset()147     public Point getDragVisualizeOffset() {
148         return mDragVisualizeOffset;
149     }
150 
setDragRegion(Rect r)151     public void setDragRegion(Rect r) {
152         mDragRegion = r;
153     }
154 
getDragRegion()155     public Rect getDragRegion() {
156         return mDragRegion;
157     }
158 
getInitialScale()159     public float getInitialScale() {
160         return mInitialScale;
161     }
162 
updateInitialScaleToCurrentScale()163     public void updateInitialScaleToCurrentScale() {
164         mInitialScale = getScaleX();
165     }
166 
167     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)168     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
169         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
170     }
171 
172     @Override
onDraw(Canvas canvas)173     protected void onDraw(Canvas canvas) {
174         @SuppressWarnings("all") // suppress dead code warning
175         final boolean debug = false;
176         if (debug) {
177             Paint p = new Paint();
178             p.setStyle(Paint.Style.FILL);
179             p.setColor(0x66ffffff);
180             canvas.drawRect(0, 0, getWidth(), getHeight(), p);
181         }
182 
183         mHasDrawn = true;
184         boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
185         if (crossFade) {
186             int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
187             mPaint.setAlpha(alpha);
188         }
189         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
190         if (crossFade) {
191             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
192             canvas.save();
193             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
194             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
195             canvas.scale(sX, sY);
196             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
197             canvas.restore();
198         }
199     }
200 
setCrossFadeBitmap(Bitmap crossFadeBitmap)201     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
202         mCrossFadeBitmap = crossFadeBitmap;
203     }
204 
crossFade(int duration)205     public void crossFade(int duration) {
206         ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
207         va.setDuration(duration);
208         va.setInterpolator(new DecelerateInterpolator(1.5f));
209         va.addUpdateListener(new AnimatorUpdateListener() {
210             @Override
211             public void onAnimationUpdate(ValueAnimator animation) {
212                 mCrossFadeProgress = animation.getAnimatedFraction();
213             }
214         });
215         va.start();
216     }
217 
setColor(int color)218     public void setColor(int color) {
219         if (mPaint == null) {
220             mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
221         }
222         if (color != 0) {
223             mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
224         } else {
225             mPaint.setColorFilter(null);
226         }
227         invalidate();
228     }
229 
hasDrawn()230     public boolean hasDrawn() {
231         return mHasDrawn;
232     }
233 
234     @Override
setAlpha(float alpha)235     public void setAlpha(float alpha) {
236         super.setAlpha(alpha);
237         mPaint.setAlpha((int) (255 * alpha));
238         invalidate();
239     }
240 
241     /**
242      * Create a window containing this view and show it.
243      *
244      * @param windowToken obtained from v.getWindowToken() from one of your views
245      * @param touchX the x coordinate the user touched in DragLayer coordinates
246      * @param touchY the y coordinate the user touched in DragLayer coordinates
247      */
show(int touchX, int touchY)248     public void show(int touchX, int touchY) {
249         mDragLayer.addView(this);
250 
251         // Start the pick-up animation
252         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
253         lp.width = mBitmap.getWidth();
254         lp.height = mBitmap.getHeight();
255         lp.customPosition = true;
256         setLayoutParams(lp);
257         setTranslationX(touchX - mRegistrationX);
258         setTranslationY(touchY - mRegistrationY);
259         // Post the animation to skip other expensive work happening on the first frame
260         post(new Runnable() {
261                 public void run() {
262                     mAnim.start();
263                 }
264             });
265     }
266 
cancelAnimation()267     public void cancelAnimation() {
268         if (mAnim != null && mAnim.isRunning()) {
269             mAnim.cancel();
270         }
271     }
272 
resetLayoutParams()273     public void resetLayoutParams() {
274         mOffsetX = mOffsetY = 0;
275         requestLayout();
276     }
277 
278     /**
279      * Move the window containing this view.
280      *
281      * @param touchX the x coordinate the user touched in DragLayer coordinates
282      * @param touchY the y coordinate the user touched in DragLayer coordinates
283      */
move(int touchX, int touchY)284     void move(int touchX, int touchY) {
285         setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
286         setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
287     }
288 
remove()289     void remove() {
290         if (getParent() != null) {
291             mDragLayer.removeView(DragView.this);
292         }
293     }
294 }
295 
296