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 */ 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. */ 150 public void setIntrinsicIconScaleFactor(float scale) { 151 mIntrinsicIconScale = scale; 152 } 153 154 public float getIntrinsicIconScaleFactor() { 155 return mIntrinsicIconScale; 156 } 157 158 public int getDragRegionLeft() { 159 return mDragRegion.left; 160 } 161 162 public int getDragRegionTop() { 163 return mDragRegion.top; 164 } 165 166 public int getDragRegionWidth() { 167 return mDragRegion.width(); 168 } 169 170 public int getDragRegionHeight() { 171 return mDragRegion.height(); 172 } 173 174 public void setDragVisualizeOffset(Point p) { 175 mDragVisualizeOffset = p; 176 } 177 178 public Point getDragVisualizeOffset() { 179 return mDragVisualizeOffset; 180 } 181 182 public void setDragRegion(Rect r) { 183 mDragRegion = r; 184 } 185 186 public Rect getDragRegion() { 187 return mDragRegion; 188 } 189 190 @Override 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") 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. 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 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 235 public void setCrossFadeBitmap(Bitmap crossFadeBitmap) { 236 mCrossFadeBitmap = crossFadeBitmap; 237 } 238 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 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 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 297 public boolean hasDrawn() { 298 return mHasDrawn; 299 } 300 301 @Override 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 */ 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 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 */ 345 public void move(int touchX, int touchY) { 346 mLastTouchX = touchX; 347 mLastTouchY = touchY; 348 applyTranslation(); 349 } 350 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 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 376 private void applyTranslation() { 377 setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX); 378 setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY); 379 } 380 381 public void remove() { 382 if (getParent() != null) { 383 mDragLayer.removeView(DragView.this); 384 } 385 } 386 387 public int getBlurSizeOutline() { 388 return mBlurSizeOutline; 389 } 390 391 public float getInitialScale() { 392 return mInitialScale; 393 } 394 } 395