• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.dragndrop;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.FloatArrayEvaluator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.annotation.SuppressLint;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.ColorMatrix;
28 import android.graphics.ColorMatrixColorFilter;
29 import android.graphics.Paint;
30 import android.graphics.Point;
31 import android.graphics.Rect;
32 import android.view.View;
33 import android.view.animation.DecelerateInterpolator;
34 
35 import com.android.launcher3.Launcher;
36 import com.android.launcher3.LauncherAnimUtils;
37 import com.android.launcher3.R;
38 import com.android.launcher3.util.Themes;
39 import com.android.launcher3.util.Thunk;
40 
41 import java.util.Arrays;
42 
43 public class DragView extends View {
44     public static final int COLOR_CHANGE_DURATION = 120;
45     public static final int VIEW_ZOOM_DURATION = 150;
46 
47     @Thunk static float sDragAlpha = 1f;
48 
49     private Bitmap mBitmap;
50     private Bitmap mCrossFadeBitmap;
51     @Thunk Paint mPaint;
52     private final int mBlurSizeOutline;
53     private final int mRegistrationX;
54     private final int mRegistrationY;
55     private final float mInitialScale;
56     private final int[] mTempLoc = new int[2];
57 
58     private Point mDragVisualizeOffset = null;
59     private Rect mDragRegion = null;
60     private final DragLayer mDragLayer;
61     @Thunk final DragController mDragController;
62     private boolean mHasDrawn = false;
63     @Thunk float mCrossFadeProgress = 0f;
64     private boolean mAnimationCancelled = false;
65 
66     ValueAnimator mAnim;
67     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
68     // size.  This is ignored for non-icons.
69     private float mIntrinsicIconScale = 1f;
70 
71     @Thunk float[] mCurrentFilter;
72     private ValueAnimator mFilterAnimator;
73 
74     private int mLastTouchX;
75     private int mLastTouchY;
76     private int mAnimatedShiftX;
77     private int mAnimatedShiftY;
78 
79     /**
80      * Construct the drag view.
81      * <p>
82      * The registration point is the point inside our view that the touch events should
83      * be centered upon.
84      * @param launcher The Launcher instance
85      * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
86      * @param registrationX The x coordinate of the registration point.
87      * @param registrationY The y coordinate of the registration point.
88      */
DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, final float initialScale, final float finalScaleDps)89     public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
90                     final float initialScale, final float finalScaleDps) {
91         super(launcher);
92         mDragLayer = launcher.getDragLayer();
93         mDragController = launcher.getDragController();
94 
95         final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
96 
97         // Set the initial scale to avoid any jumps
98         setScaleX(initialScale);
99         setScaleY(initialScale);
100 
101         // Animate the view into the correct position
102         mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
103         mAnim.setDuration(VIEW_ZOOM_DURATION);
104         mAnim.addUpdateListener(new AnimatorUpdateListener() {
105             @Override
106             public void onAnimationUpdate(ValueAnimator animation) {
107                 final float value = (Float) animation.getAnimatedValue();
108 
109                 setScaleX(initialScale + (value * (scale - initialScale)));
110                 setScaleY(initialScale + (value * (scale - initialScale)));
111                 if (sDragAlpha != 1f) {
112                     setAlpha(sDragAlpha * value + (1f - value));
113                 }
114 
115                 if (getParent() == null) {
116                     animation.cancel();
117                 }
118             }
119         });
120 
121         mAnim.addListener(new AnimatorListenerAdapter() {
122             @Override
123             public void onAnimationEnd(Animator animation) {
124                 if (!mAnimationCancelled) {
125                     mDragController.onDragViewAnimationEnd();
126                 }
127             }
128         });
129 
130         mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());
131         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
132 
133         // The point in our scaled bitmap that the touch events are located
134         mRegistrationX = registrationX;
135         mRegistrationY = registrationY;
136 
137         mInitialScale = initialScale;
138 
139         // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
140         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
141         measure(ms, ms);
142         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
143 
144         mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
145 
146         setElevation(getResources().getDimension(R.dimen.drag_elevation));
147     }
148 
149     /** Sets the scale of the view over the normal workspace icon size. */
setIntrinsicIconScaleFactor(float scale)150     public void setIntrinsicIconScaleFactor(float scale) {
151         mIntrinsicIconScale = scale;
152     }
153 
getIntrinsicIconScaleFactor()154     public float getIntrinsicIconScaleFactor() {
155         return mIntrinsicIconScale;
156     }
157 
getDragRegionLeft()158     public int getDragRegionLeft() {
159         return mDragRegion.left;
160     }
161 
getDragRegionTop()162     public int getDragRegionTop() {
163         return mDragRegion.top;
164     }
165 
getDragRegionWidth()166     public int getDragRegionWidth() {
167         return mDragRegion.width();
168     }
169 
getDragRegionHeight()170     public int getDragRegionHeight() {
171         return mDragRegion.height();
172     }
173 
setDragVisualizeOffset(Point p)174     public void setDragVisualizeOffset(Point p) {
175         mDragVisualizeOffset = p;
176     }
177 
getDragVisualizeOffset()178     public Point getDragVisualizeOffset() {
179         return mDragVisualizeOffset;
180     }
181 
setDragRegion(Rect r)182     public void setDragRegion(Rect r) {
183         mDragRegion = r;
184     }
185 
getDragRegion()186     public Rect getDragRegion() {
187         return mDragRegion;
188     }
189 
190     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)191     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
192         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
193     }
194 
195     // Draws drag shadow for system DND.
196     @SuppressLint("WrongCall")
drawDragShadow(Canvas canvas)197     public void drawDragShadow(Canvas canvas) {
198         final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
199         canvas.scale(getScaleX(), getScaleY());
200         onDraw(canvas);
201         canvas.restoreToCount(saveCount);
202     }
203 
204     // Provides drag shadow metrics for system DND.
provideDragShadowMetrics(Point size, Point touch)205     public void provideDragShadowMetrics(Point size, Point touch) {
206         size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY()));
207 
208         final float xGrowth = mBitmap.getWidth() * (getScaleX() - 1);
209         final float yGrowth = mBitmap.getHeight() * (getScaleY() - 1);
210         touch.set(
211                 mRegistrationX + (int)Math.round(xGrowth / 2),
212                 mRegistrationY + (int)Math.round(yGrowth / 2));
213     }
214 
215     @Override
onDraw(Canvas canvas)216     protected void onDraw(Canvas canvas) {
217         mHasDrawn = true;
218         boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
219         if (crossFade) {
220             int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
221             mPaint.setAlpha(alpha);
222         }
223         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
224         if (crossFade) {
225             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
226             final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
227             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
228             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
229             canvas.scale(sX, sY);
230             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
231             canvas.restoreToCount(saveCount);
232         }
233     }
234 
setCrossFadeBitmap(Bitmap crossFadeBitmap)235     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
236         mCrossFadeBitmap = crossFadeBitmap;
237     }
238 
crossFade(int duration)239     public void crossFade(int duration) {
240         ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
241         va.setDuration(duration);
242         va.setInterpolator(new DecelerateInterpolator(1.5f));
243         va.addUpdateListener(new AnimatorUpdateListener() {
244             @Override
245             public void onAnimationUpdate(ValueAnimator animation) {
246                 mCrossFadeProgress = animation.getAnimatedFraction();
247                 invalidate();
248             }
249         });
250         va.start();
251     }
252 
setColor(int color)253     public void setColor(int color) {
254         if (mPaint == null) {
255             mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
256         }
257         if (color != 0) {
258             ColorMatrix m1 = new ColorMatrix();
259             m1.setSaturation(0);
260 
261             ColorMatrix m2 = new ColorMatrix();
262             Themes.setColorScaleOnMatrix(color, m2);
263             m1.postConcat(m2);
264 
265             animateFilterTo(m1.getArray());
266         } else {
267             if (mCurrentFilter == null) {
268                 mPaint.setColorFilter(null);
269                 invalidate();
270             } else {
271                 animateFilterTo(new ColorMatrix().getArray());
272             }
273         }
274     }
275 
animateFilterTo(float[] targetFilter)276     private void animateFilterTo(float[] targetFilter) {
277         float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
278         mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
279 
280         if (mFilterAnimator != null) {
281             mFilterAnimator.cancel();
282         }
283         mFilterAnimator = ValueAnimator.ofObject(new FloatArrayEvaluator(mCurrentFilter),
284                 oldFilter, targetFilter);
285         mFilterAnimator.setDuration(COLOR_CHANGE_DURATION);
286         mFilterAnimator.addUpdateListener(new AnimatorUpdateListener() {
287 
288             @Override
289             public void onAnimationUpdate(ValueAnimator animation) {
290                 mPaint.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
291                 invalidate();
292             }
293         });
294         mFilterAnimator.start();
295     }
296 
hasDrawn()297     public boolean hasDrawn() {
298         return mHasDrawn;
299     }
300 
301     @Override
setAlpha(float alpha)302     public void setAlpha(float alpha) {
303         super.setAlpha(alpha);
304         mPaint.setAlpha((int) (255 * alpha));
305         invalidate();
306     }
307 
308     /**
309      * Create a window containing this view and show it.
310      *
311      * @param touchX the x coordinate the user touched in DragLayer coordinates
312      * @param touchY the y coordinate the user touched in DragLayer coordinates
313      */
show(int touchX, int touchY)314     public void show(int touchX, int touchY) {
315         mDragLayer.addView(this);
316 
317         // Start the pick-up animation
318         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
319         lp.width = mBitmap.getWidth();
320         lp.height = mBitmap.getHeight();
321         lp.customPosition = true;
322         setLayoutParams(lp);
323         move(touchX, touchY);
324         // Post the animation to skip other expensive work happening on the first frame
325         post(new Runnable() {
326             public void run() {
327                 mAnim.start();
328             }
329         });
330     }
331 
cancelAnimation()332     public void cancelAnimation() {
333         mAnimationCancelled = true;
334         if (mAnim != null && mAnim.isRunning()) {
335             mAnim.cancel();
336         }
337     }
338 
339     /**
340      * Move the window containing this view.
341      *
342      * @param touchX the x coordinate the user touched in DragLayer coordinates
343      * @param touchY the y coordinate the user touched in DragLayer coordinates
344      */
move(int touchX, int touchY)345     public void move(int touchX, int touchY) {
346         mLastTouchX = touchX;
347         mLastTouchY = touchY;
348         applyTranslation();
349     }
350 
animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration)351     public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
352         mTempLoc[0] = toTouchX - mRegistrationX;
353         mTempLoc[1] = toTouchY - mRegistrationY;
354         mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mInitialScale, mInitialScale,
355                 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
356     }
357 
animateShift(final int shiftX, final int shiftY)358     public void animateShift(final int shiftX, final int shiftY) {
359         if (mAnim.isStarted()) {
360             return;
361         }
362         mAnimatedShiftX = shiftX;
363         mAnimatedShiftY = shiftY;
364         applyTranslation();
365         mAnim.addUpdateListener(new AnimatorUpdateListener() {
366             @Override
367             public void onAnimationUpdate(ValueAnimator animation) {
368                 float fraction = 1 - animation.getAnimatedFraction();
369                 mAnimatedShiftX = (int) (fraction * shiftX);
370                 mAnimatedShiftY = (int) (fraction * shiftY);
371                 applyTranslation();
372             }
373         });
374     }
375 
applyTranslation()376     private void applyTranslation() {
377         setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX);
378         setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY);
379     }
380 
remove()381     public void remove() {
382         if (getParent() != null) {
383             mDragLayer.removeView(DragView.this);
384         }
385     }
386 
getBlurSizeOutline()387     public int getBlurSizeOutline() {
388         return mBlurSizeOutline;
389     }
390 
getInitialScale()391     public float getInitialScale() {
392         return mInitialScale;
393     }
394 }
395