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