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