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