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.TargetApi; 25 import android.content.pm.LauncherActivityInfo; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.ColorMatrix; 30 import android.graphics.ColorMatrixColorFilter; 31 import android.graphics.Paint; 32 import android.graphics.Path; 33 import android.graphics.Point; 34 import android.graphics.Rect; 35 import android.graphics.drawable.AdaptiveIconDrawable; 36 import android.graphics.drawable.ColorDrawable; 37 import android.graphics.drawable.Drawable; 38 import android.graphics.drawable.InsetDrawable; 39 import android.os.Build; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.support.animation.FloatPropertyCompat; 43 import android.support.animation.SpringAnimation; 44 import android.support.animation.SpringForce; 45 import android.view.View; 46 47 import com.android.launcher3.FastBitmapDrawable; 48 import com.android.launcher3.ItemInfo; 49 import com.android.launcher3.ItemInfoWithIcon; 50 import com.android.launcher3.Launcher; 51 import com.android.launcher3.LauncherAnimUtils; 52 import com.android.launcher3.LauncherAppState; 53 import com.android.launcher3.LauncherModel; 54 import com.android.launcher3.LauncherSettings; 55 import com.android.launcher3.R; 56 import com.android.launcher3.Utilities; 57 import com.android.launcher3.anim.Interpolators; 58 import com.android.launcher3.compat.LauncherAppsCompat; 59 import com.android.launcher3.compat.ShortcutConfigActivityInfo; 60 import com.android.launcher3.config.FeatureFlags; 61 import com.android.launcher3.graphics.IconNormalizer; 62 import com.android.launcher3.graphics.LauncherIcons; 63 import com.android.launcher3.shortcuts.DeepShortcutManager; 64 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 65 import com.android.launcher3.shortcuts.ShortcutKey; 66 import com.android.launcher3.util.Themes; 67 import com.android.launcher3.util.Thunk; 68 import com.android.launcher3.widget.PendingAddShortcutInfo; 69 70 import java.util.Arrays; 71 import java.util.List; 72 73 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; 74 75 public class DragView extends View { 76 private static final ColorMatrix sTempMatrix1 = new ColorMatrix(); 77 private static final ColorMatrix sTempMatrix2 = new ColorMatrix(); 78 79 public static final int COLOR_CHANGE_DURATION = 120; 80 public static final int VIEW_ZOOM_DURATION = 150; 81 82 @Thunk static float sDragAlpha = 1f; 83 84 private boolean mDrawBitmap = true; 85 private Bitmap mBitmap; 86 private Bitmap mCrossFadeBitmap; 87 @Thunk Paint mPaint; 88 private final int mBlurSizeOutline; 89 private final int mRegistrationX; 90 private final int mRegistrationY; 91 private final float mInitialScale; 92 private final float mScaleOnDrop; 93 private final int[] mTempLoc = new int[2]; 94 95 private Point mDragVisualizeOffset = null; 96 private Rect mDragRegion = null; 97 private final Launcher mLauncher; 98 private final DragLayer mDragLayer; 99 @Thunk final DragController mDragController; 100 private boolean mHasDrawn = false; 101 @Thunk float mCrossFadeProgress = 0f; 102 private boolean mAnimationCancelled = false; 103 104 ValueAnimator mAnim; 105 // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace 106 // size. This is ignored for non-icons. 107 private float mIntrinsicIconScale = 1f; 108 109 @Thunk float[] mCurrentFilter; 110 private ValueAnimator mFilterAnimator; 111 112 private int mLastTouchX; 113 private int mLastTouchY; 114 private int mAnimatedShiftX; 115 private int mAnimatedShiftY; 116 117 // Below variable only needed IF FeatureFlags.LAUNCHER3_SPRING_ICONS is {@code true} 118 private Drawable mBgSpringDrawable, mFgSpringDrawable; 119 private SpringFloatValue mTranslateX, mTranslateY; 120 private Path mScaledMaskPath; 121 private Drawable mBadge; 122 private ColorMatrixColorFilter mBaseFilter; 123 124 /** 125 * Construct the drag view. 126 * <p> 127 * The registration point is the point inside our view that the touch events should 128 * be centered upon. 129 * @param launcher The Launcher instance 130 * @param bitmap The view that we're dragging around. We scale it up when we draw it. 131 * @param registrationX The x coordinate of the registration point. 132 * @param registrationY The y coordinate of the registration point. 133 */ 134 public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, 135 final float initialScale, final float scaleOnDrop, final float finalScaleDps) { 136 super(launcher); 137 mLauncher = launcher; 138 mDragLayer = launcher.getDragLayer(); 139 mDragController = launcher.getDragController(); 140 141 final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth(); 142 143 // Set the initial scale to avoid any jumps 144 setScaleX(initialScale); 145 setScaleY(initialScale); 146 147 // Animate the view into the correct position 148 mAnim = LauncherAnimUtils.ofFloat(0f, 1f); 149 mAnim.setDuration(VIEW_ZOOM_DURATION); 150 mAnim.addUpdateListener(new AnimatorUpdateListener() { 151 @Override 152 public void onAnimationUpdate(ValueAnimator animation) { 153 final float value = (Float) animation.getAnimatedValue(); 154 155 setScaleX(initialScale + (value * (scale - initialScale))); 156 setScaleY(initialScale + (value * (scale - initialScale))); 157 if (sDragAlpha != 1f) { 158 setAlpha(sDragAlpha * value + (1f - value)); 159 } 160 161 if (getParent() == null) { 162 animation.cancel(); 163 } 164 } 165 }); 166 167 mAnim.addListener(new AnimatorListenerAdapter() { 168 @Override 169 public void onAnimationEnd(Animator animation) { 170 if (!mAnimationCancelled) { 171 mDragController.onDragViewAnimationEnd(); 172 } 173 } 174 }); 175 176 mBitmap = bitmap; 177 setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())); 178 179 // The point in our scaled bitmap that the touch events are located 180 mRegistrationX = registrationX; 181 mRegistrationY = registrationY; 182 183 mInitialScale = initialScale; 184 mScaleOnDrop = scaleOnDrop; 185 186 // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass 187 int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 188 measure(ms, ms); 189 mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 190 191 mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 192 setElevation(getResources().getDimension(R.dimen.drag_elevation)); 193 } 194 195 /** 196 * Initialize {@code #mIconDrawable} if the item can be represented using 197 * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}. 198 */ 199 @TargetApi(Build.VERSION_CODES.O) 200 public void setItemInfo(final ItemInfo info) { 201 if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.ATLEAST_OREO)) { 202 return; 203 } 204 if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 205 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT && 206 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { 207 return; 208 } 209 // Load the adaptive icon on a background thread and add the view in ui thread. 210 final Looper workerLooper = LauncherModel.getWorkerLooper(); 211 new Handler(workerLooper).postAtFrontOfQueue(new Runnable() { 212 @Override 213 public void run() { 214 LauncherAppState appState = LauncherAppState.getInstance(mLauncher); 215 Object[] outObj = new Object[1]; 216 final Drawable dr = getFullDrawable(info, appState, outObj); 217 218 if (dr instanceof AdaptiveIconDrawable) { 219 int w = mBitmap.getWidth(); 220 int h = mBitmap.getHeight(); 221 int blurMargin = (int) mLauncher.getResources() 222 .getDimension(R.dimen.blur_size_medium_outline) / 2; 223 224 Rect bounds = new Rect(0, 0, w, h); 225 bounds.inset(blurMargin, blurMargin); 226 // Badge is applied after icon normalization so the bounds for badge should not 227 // be scaled down due to icon normalization. 228 Rect badgeBounds = new Rect(bounds); 229 mBadge = getBadge(info, appState, outObj[0]); 230 mBadge.setBounds(badgeBounds); 231 232 LauncherIcons li = LauncherIcons.obtain(mLauncher); 233 Utilities.scaleRectAboutCenter(bounds, 234 li.getNormalizer().getScale(dr, null, null, null)); 235 li.recycle(); 236 AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr; 237 238 // Shrink very tiny bit so that the clip path is smaller than the original bitmap 239 // that has anti aliased edges and shadows. 240 Rect shrunkBounds = new Rect(bounds); 241 Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f); 242 adaptiveIcon.setBounds(shrunkBounds); 243 final Path mask = adaptiveIcon.getIconMask(); 244 245 mTranslateX = new SpringFloatValue(DragView.this, 246 w * AdaptiveIconDrawable.getExtraInsetFraction()); 247 mTranslateY = new SpringFloatValue(DragView.this, 248 h * AdaptiveIconDrawable.getExtraInsetFraction()); 249 250 bounds.inset( 251 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()), 252 (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction()) 253 ); 254 mBgSpringDrawable = adaptiveIcon.getBackground(); 255 if (mBgSpringDrawable == null) { 256 mBgSpringDrawable = new ColorDrawable(Color.TRANSPARENT); 257 } 258 mBgSpringDrawable.setBounds(bounds); 259 mFgSpringDrawable = adaptiveIcon.getForeground(); 260 if (mFgSpringDrawable == null) { 261 mFgSpringDrawable = new ColorDrawable(Color.TRANSPARENT); 262 } 263 mFgSpringDrawable.setBounds(bounds); 264 265 new Handler(Looper.getMainLooper()).post(new Runnable() { 266 @Override 267 public void run() { 268 // Assign the variable on the UI thread to avoid race conditions. 269 mScaledMaskPath = mask; 270 271 // Do not draw the background in case of folder as its translucent 272 mDrawBitmap = !(dr instanceof FolderAdaptiveIcon); 273 274 if (info.isDisabled()) { 275 FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null); 276 d.setIsDisabled(true); 277 mBaseFilter = (ColorMatrixColorFilter) d.getColorFilter(); 278 } 279 updateColorFilter(); 280 } 281 }); 282 } 283 }}); 284 } 285 286 @TargetApi(Build.VERSION_CODES.O) 287 private void updateColorFilter() { 288 if (mCurrentFilter == null) { 289 mPaint.setColorFilter(null); 290 291 if (mScaledMaskPath != null) { 292 mBgSpringDrawable.setColorFilter(mBaseFilter); 293 mFgSpringDrawable.setColorFilter(mBaseFilter); 294 mBadge.setColorFilter(mBaseFilter); 295 } 296 } else { 297 ColorMatrixColorFilter currentFilter = new ColorMatrixColorFilter(mCurrentFilter); 298 mPaint.setColorFilter(currentFilter); 299 300 if (mScaledMaskPath != null) { 301 if (mBaseFilter != null) { 302 mBaseFilter.getColorMatrix(sTempMatrix1); 303 sTempMatrix2.set(mCurrentFilter); 304 sTempMatrix1.postConcat(sTempMatrix2); 305 306 currentFilter = new ColorMatrixColorFilter(sTempMatrix1); 307 } 308 309 mBgSpringDrawable.setColorFilter(currentFilter); 310 mFgSpringDrawable.setColorFilter(currentFilter); 311 mBadge.setColorFilter(currentFilter); 312 } 313 } 314 315 invalidate(); 316 } 317 318 /** 319 * Returns the full drawable for {@param info}. 320 * @param outObj this is set to the internal data associated with {@param info}, 321 * eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}. 322 */ 323 private Drawable getFullDrawable(ItemInfo info, LauncherAppState appState, Object[] outObj) { 324 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 325 LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(mLauncher) 326 .resolveActivity(info.getIntent(), info.user); 327 outObj[0] = activityInfo; 328 return (activityInfo != null) ? appState.getIconCache() 329 .getFullResIcon(activityInfo, false) : null; 330 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 331 if (info instanceof PendingAddShortcutInfo) { 332 ShortcutConfigActivityInfo activityInfo = 333 ((PendingAddShortcutInfo) info).activityInfo; 334 outObj[0] = activityInfo; 335 return activityInfo.getFullResIcon(appState.getIconCache()); 336 } 337 ShortcutKey key = ShortcutKey.fromItemInfo(info); 338 DeepShortcutManager sm = DeepShortcutManager.getInstance(mLauncher); 339 List<ShortcutInfoCompat> si = sm.queryForFullDetails( 340 key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user); 341 if (si.isEmpty()) { 342 return null; 343 } else { 344 outObj[0] = si.get(0); 345 return sm.getShortcutIconDrawable(si.get(0), 346 appState.getInvariantDeviceProfile().fillResIconDpi); 347 } 348 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { 349 FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon( 350 mLauncher, info.id, new Point(mBitmap.getWidth(), mBitmap.getHeight())); 351 if (icon == null) { 352 return null; 353 } 354 outObj[0] = icon; 355 return icon; 356 } else { 357 return null; 358 } 359 } 360 361 /** 362 * For apps icons and shortcut icons that have badges, this method creates a drawable that can 363 * later on be rendered on top of the layers for the badges. For app icons, work profile badges 364 * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no 365 * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge 366 **/ 367 368 @TargetApi(Build.VERSION_CODES.O) 369 private Drawable getBadge(ItemInfo info, LauncherAppState appState, Object obj) { 370 int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize; 371 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 372 boolean iconBadged = (info instanceof ItemInfoWithIcon) 373 && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0; 374 if ((info.id == ItemInfo.NO_ID && !iconBadged) 375 || !(obj instanceof ShortcutInfoCompat)) { 376 // The item is not yet added on home screen. 377 return new FixedSizeEmptyDrawable(iconSize); 378 } 379 ShortcutInfoCompat si = (ShortcutInfoCompat) obj; 380 LauncherIcons li = LauncherIcons.obtain(appState.getContext()); 381 Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap; 382 li.recycle(); 383 float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size); 384 float insetFraction = (iconSize - badgeSize) / iconSize; 385 return new InsetDrawable(new FastBitmapDrawable(badge), 386 insetFraction, insetFraction, 0, 0); 387 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { 388 return ((FolderAdaptiveIcon) obj).getBadge(); 389 } else { 390 return mLauncher.getPackageManager() 391 .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user); 392 } 393 } 394 395 @Override 396 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 397 setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); 398 } 399 400 /** Sets the scale of the view over the normal workspace icon size. */ 401 public void setIntrinsicIconScaleFactor(float scale) { 402 mIntrinsicIconScale = scale; 403 } 404 405 public float getIntrinsicIconScaleFactor() { 406 return mIntrinsicIconScale; 407 } 408 409 public int getDragRegionLeft() { 410 return mDragRegion.left; 411 } 412 413 public int getDragRegionTop() { 414 return mDragRegion.top; 415 } 416 417 public int getDragRegionWidth() { 418 return mDragRegion.width(); 419 } 420 421 public int getDragRegionHeight() { 422 return mDragRegion.height(); 423 } 424 425 public void setDragVisualizeOffset(Point p) { 426 mDragVisualizeOffset = p; 427 } 428 429 public Point getDragVisualizeOffset() { 430 return mDragVisualizeOffset; 431 } 432 433 public void setDragRegion(Rect r) { 434 mDragRegion = r; 435 } 436 437 public Rect getDragRegion() { 438 return mDragRegion; 439 } 440 441 public Bitmap getPreviewBitmap() { 442 return mBitmap; 443 } 444 445 @Override 446 protected void onDraw(Canvas canvas) { 447 mHasDrawn = true; 448 449 if (mDrawBitmap) { 450 // Always draw the bitmap to mask anti aliasing due to clipPath 451 boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null; 452 if (crossFade) { 453 int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255; 454 mPaint.setAlpha(alpha); 455 } 456 canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); 457 if (crossFade) { 458 mPaint.setAlpha((int) (255 * mCrossFadeProgress)); 459 final int saveCount = canvas.save(); 460 float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth(); 461 float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight(); 462 canvas.scale(sX, sY); 463 canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint); 464 canvas.restoreToCount(saveCount); 465 } 466 } 467 468 if (mScaledMaskPath != null) { 469 int cnt = canvas.save(); 470 canvas.clipPath(mScaledMaskPath); 471 mBgSpringDrawable.draw(canvas); 472 canvas.translate(mTranslateX.mValue, mTranslateY.mValue); 473 mFgSpringDrawable.draw(canvas); 474 canvas.restoreToCount(cnt); 475 mBadge.draw(canvas); 476 } 477 } 478 479 public void setCrossFadeBitmap(Bitmap crossFadeBitmap) { 480 mCrossFadeBitmap = crossFadeBitmap; 481 } 482 483 public void crossFade(int duration) { 484 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f); 485 va.setDuration(duration); 486 va.setInterpolator(Interpolators.DEACCEL_1_5); 487 va.addUpdateListener(new AnimatorUpdateListener() { 488 @Override 489 public void onAnimationUpdate(ValueAnimator animation) { 490 mCrossFadeProgress = animation.getAnimatedFraction(); 491 invalidate(); 492 } 493 }); 494 va.start(); 495 } 496 497 public void setColor(int color) { 498 if (mPaint == null) { 499 mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 500 } 501 if (color != 0) { 502 ColorMatrix m1 = new ColorMatrix(); 503 m1.setSaturation(0); 504 505 ColorMatrix m2 = new ColorMatrix(); 506 Themes.setColorScaleOnMatrix(color, m2); 507 m1.postConcat(m2); 508 509 animateFilterTo(m1.getArray()); 510 } else { 511 if (mCurrentFilter == null) { 512 updateColorFilter(); 513 } else { 514 animateFilterTo(new ColorMatrix().getArray()); 515 } 516 } 517 } 518 519 private void animateFilterTo(float[] targetFilter) { 520 float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter; 521 mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length); 522 523 if (mFilterAnimator != null) { 524 mFilterAnimator.cancel(); 525 } 526 mFilterAnimator = ValueAnimator.ofObject(new FloatArrayEvaluator(mCurrentFilter), 527 oldFilter, targetFilter); 528 mFilterAnimator.setDuration(COLOR_CHANGE_DURATION); 529 mFilterAnimator.addUpdateListener(new AnimatorUpdateListener() { 530 531 @Override 532 public void onAnimationUpdate(ValueAnimator animation) { 533 updateColorFilter(); 534 } 535 }); 536 mFilterAnimator.start(); 537 } 538 539 public boolean hasDrawn() { 540 return mHasDrawn; 541 } 542 543 @Override 544 public void setAlpha(float alpha) { 545 super.setAlpha(alpha); 546 mPaint.setAlpha((int) (255 * alpha)); 547 invalidate(); 548 } 549 550 /** 551 * Create a window containing this view and show it. 552 * 553 * @param touchX the x coordinate the user touched in DragLayer coordinates 554 * @param touchY the y coordinate the user touched in DragLayer coordinates 555 */ 556 public void show(int touchX, int touchY) { 557 mDragLayer.addView(this); 558 559 // Start the pick-up animation 560 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0); 561 lp.width = mBitmap.getWidth(); 562 lp.height = mBitmap.getHeight(); 563 lp.customPosition = true; 564 setLayoutParams(lp); 565 move(touchX, touchY); 566 // Post the animation to skip other expensive work happening on the first frame 567 post(new Runnable() { 568 public void run() { 569 mAnim.start(); 570 } 571 }); 572 } 573 574 public void cancelAnimation() { 575 mAnimationCancelled = true; 576 if (mAnim != null && mAnim.isRunning()) { 577 mAnim.cancel(); 578 } 579 } 580 581 /** 582 * Move the window containing this view. 583 * 584 * @param touchX the x coordinate the user touched in DragLayer coordinates 585 * @param touchY the y coordinate the user touched in DragLayer coordinates 586 */ 587 public void move(int touchX, int touchY) { 588 if (touchX > 0 && touchY > 0 && mLastTouchX > 0 && mLastTouchY > 0 589 && mScaledMaskPath != null) { 590 mTranslateX.animateToPos(mLastTouchX - touchX); 591 mTranslateY.animateToPos(mLastTouchY - touchY); 592 } 593 mLastTouchX = touchX; 594 mLastTouchY = touchY; 595 applyTranslation(); 596 } 597 598 public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) { 599 mTempLoc[0] = toTouchX - mRegistrationX; 600 mTempLoc[1] = toTouchY - mRegistrationY; 601 mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop, mScaleOnDrop, 602 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 603 } 604 605 public void animateShift(final int shiftX, final int shiftY) { 606 if (mAnim.isStarted()) { 607 return; 608 } 609 mAnimatedShiftX = shiftX; 610 mAnimatedShiftY = shiftY; 611 applyTranslation(); 612 mAnim.addUpdateListener(new AnimatorUpdateListener() { 613 @Override 614 public void onAnimationUpdate(ValueAnimator animation) { 615 float fraction = 1 - animation.getAnimatedFraction(); 616 mAnimatedShiftX = (int) (fraction * shiftX); 617 mAnimatedShiftY = (int) (fraction * shiftY); 618 applyTranslation(); 619 } 620 }); 621 } 622 623 private void applyTranslation() { 624 setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX); 625 setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY); 626 } 627 628 public void remove() { 629 if (getParent() != null) { 630 mDragLayer.removeView(DragView.this); 631 } 632 } 633 634 public int getBlurSizeOutline() { 635 return mBlurSizeOutline; 636 } 637 638 public float getInitialScale() { 639 return mInitialScale; 640 } 641 642 private static class SpringFloatValue { 643 644 private static final FloatPropertyCompat<SpringFloatValue> VALUE = 645 new FloatPropertyCompat<SpringFloatValue>("value") { 646 @Override 647 public float getValue(SpringFloatValue object) { 648 return object.mValue; 649 } 650 651 @Override 652 public void setValue(SpringFloatValue object, float value) { 653 object.mValue = value; 654 object.mView.invalidate(); 655 } 656 }; 657 658 // Following three values are fine tuned with motion ux designer 659 private final static int STIFFNESS = 4000; 660 private final static float DAMPENING_RATIO = 1f; 661 private final static int PARALLAX_MAX_IN_DP = 8; 662 663 private final View mView; 664 private final SpringAnimation mSpring; 665 private final float mDelta; 666 667 private float mValue; 668 669 public SpringFloatValue(View view, float range) { 670 mView = view; 671 mSpring = new SpringAnimation(this, VALUE, 0) 672 .setMinValue(-range).setMaxValue(range) 673 .setSpring(new SpringForce(0) 674 .setDampingRatio(DAMPENING_RATIO) 675 .setStiffness(STIFFNESS)); 676 mDelta = view.getResources().getDisplayMetrics().density * PARALLAX_MAX_IN_DP; 677 } 678 679 public void animateToPos(float value) { 680 mSpring.animateToFinalPosition(Utilities.boundToRange(value, -mDelta, mDelta)); 681 } 682 } 683 684 private static class FixedSizeEmptyDrawable extends ColorDrawable { 685 686 private final int mSize; 687 688 public FixedSizeEmptyDrawable(int size) { 689 super(Color.TRANSPARENT); 690 mSize = size; 691 } 692 693 @Override 694 public int getIntrinsicHeight() { 695 return mSize; 696 } 697 698 @Override 699 public int getIntrinsicWidth() { 700 return mSize; 701 } 702 } 703 } 704