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