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 static android.view.View.MeasureSpec.EXACTLY; 20 import static android.view.View.MeasureSpec.makeMeasureSpec; 21 22 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 23 import static com.android.launcher3.Utilities.getBadge; 24 import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter; 25 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 26 27 import android.animation.Animator; 28 import android.animation.AnimatorListenerAdapter; 29 import android.animation.AnimatorSet; 30 import android.animation.ObjectAnimator; 31 import android.animation.ValueAnimator; 32 import android.animation.ValueAnimator.AnimatorUpdateListener; 33 import android.annotation.TargetApi; 34 import android.content.Context; 35 import android.graphics.Canvas; 36 import android.graphics.Color; 37 import android.graphics.ColorFilter; 38 import android.graphics.Path; 39 import android.graphics.Picture; 40 import android.graphics.Rect; 41 import android.graphics.drawable.AdaptiveIconDrawable; 42 import android.graphics.drawable.ColorDrawable; 43 import android.graphics.drawable.Drawable; 44 import android.graphics.drawable.PictureDrawable; 45 import android.os.Build; 46 import android.os.Handler; 47 import android.os.Looper; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.widget.FrameLayout; 51 import android.widget.ImageView; 52 53 import androidx.annotation.Nullable; 54 import androidx.dynamicanimation.animation.FloatPropertyCompat; 55 import androidx.dynamicanimation.animation.SpringAnimation; 56 import androidx.dynamicanimation.animation.SpringForce; 57 58 import com.android.app.animation.Interpolators; 59 import com.android.launcher3.R; 60 import com.android.launcher3.Utilities; 61 import com.android.launcher3.icons.FastBitmapDrawable; 62 import com.android.launcher3.icons.LauncherIcons; 63 import com.android.launcher3.model.data.ItemInfo; 64 import com.android.launcher3.util.RunnableList; 65 import com.android.launcher3.views.ActivityContext; 66 import com.android.launcher3.views.BaseDragLayer; 67 68 /** A custom view for rendering an icon, folder, shortcut or widget during drag-n-drop. */ 69 public abstract class DragView<T extends Context & ActivityContext> extends FrameLayout { 70 71 public static final int VIEW_ZOOM_DURATION = 150; 72 73 private final View mContent; 74 // The following are only used for rendering mContent directly during drag-n-drop. 75 @Nullable private ViewGroup.LayoutParams mContentViewLayoutParams; 76 @Nullable private ViewGroup mContentViewParent; 77 private int mContentViewInParentViewIndex = -1; 78 private final int mWidth; 79 private final int mHeight; 80 81 private final int mBlurSizeOutline; 82 protected final int mRegistrationX; 83 protected final int mRegistrationY; 84 private final float mInitialScale; 85 private final float mEndScale; 86 protected final float mScaleOnDrop; 87 protected final int[] mTempLoc = new int[2]; 88 89 private final RunnableList mOnDragStartCallback = new RunnableList(); 90 91 private boolean mHasDragOffset; 92 private Rect mDragRegion = null; 93 protected final T mActivity; 94 private final BaseDragLayer<T> mDragLayer; 95 private boolean mHasDrawn = false; 96 97 final ValueAnimator mScaleAnim; 98 final ValueAnimator mShiftAnim; 99 100 // Whether mAnim has started. Unlike mAnim.isStarted(), this is true even after mAnim ends. 101 private boolean mScaleAnimStarted; 102 private boolean mShiftAnimStarted; 103 private Runnable mOnScaleAnimEndCallback; 104 private Runnable mOnShiftAnimEndCallback; 105 106 private int mLastTouchX; 107 private int mLastTouchY; 108 private int mAnimatedShiftX; 109 private int mAnimatedShiftY; 110 111 // Below variable only needed IF FeatureFlags.LAUNCHER3_SPRING_ICONS is {@code true} 112 private Drawable mBgSpringDrawable, mFgSpringDrawable; 113 private SpringFloatValue mTranslateX, mTranslateY; 114 private Path mScaledMaskPath; 115 private Drawable mBadge; 116 DragView(T launcher, Drawable drawable, int registrationX, int registrationY, final float initialScale, final float scaleOnDrop, final float finalScaleDps)117 public DragView(T launcher, Drawable drawable, int registrationX, 118 int registrationY, final float initialScale, final float scaleOnDrop, 119 final float finalScaleDps) { 120 this(launcher, getViewFromDrawable(launcher, drawable), 121 drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), 122 registrationX, registrationY, initialScale, scaleOnDrop, finalScaleDps); 123 } 124 125 /** 126 * Construct the drag view. 127 * <p> 128 * The registration point is the point inside our view that the touch events should 129 * be centered upon. 130 * @param activity The Launcher instance/ActivityContext this DragView is in. 131 * @param content the view content that is attached to the drag view. 132 * @param width the width of the dragView 133 * @param height the height of the dragView 134 * @param initialScale The view that we're dragging around. We scale it up when we draw it. 135 * @param registrationX The x coordinate of the registration point. 136 * @param registrationY The y coordinate of the registration point. 137 * @param scaleOnDrop the scale used in the drop animation. 138 * @param finalScaleDps the scale used in the zoom out animation when the drag view is shown. 139 */ DragView(T activity, View content, int width, int height, int registrationX, int registrationY, final float initialScale, final float scaleOnDrop, final float finalScaleDps)140 public DragView(T activity, View content, int width, int height, int registrationX, 141 int registrationY, final float initialScale, final float scaleOnDrop, 142 final float finalScaleDps) { 143 super(activity); 144 mActivity = activity; 145 mDragLayer = activity.getDragLayer(); 146 147 mContent = content; 148 mWidth = width; 149 mHeight = height; 150 mContentViewLayoutParams = mContent.getLayoutParams(); 151 if (mContent.getParent() instanceof ViewGroup) { 152 mContentViewParent = (ViewGroup) mContent.getParent(); 153 mContentViewInParentViewIndex = mContentViewParent.indexOfChild(mContent); 154 mContentViewParent.removeView(mContent); 155 } 156 157 addView(content, new LayoutParams(width, height)); 158 159 // If there is already a scale set on the content, we don't want to clip the children. 160 if (content.getScaleX() != 1 || content.getScaleY() != 1) { 161 setClipChildren(false); 162 setClipToPadding(false); 163 } 164 165 mEndScale = (width + finalScaleDps) / width; 166 167 // Set the initial scale to avoid any jumps 168 setScaleX(initialScale); 169 setScaleY(initialScale); 170 171 // Animate the view into the correct position 172 mScaleAnim = ValueAnimator.ofFloat(0f, 1f); 173 mScaleAnim.setDuration(VIEW_ZOOM_DURATION); 174 mScaleAnim.addUpdateListener(animation -> { 175 final float value = (Float) animation.getAnimatedValue(); 176 setScaleX(Utilities.mapRange(value, initialScale, mEndScale)); 177 setScaleY(Utilities.mapRange(value, initialScale, mEndScale)); 178 if (!isAttachedToWindow()) { 179 animation.cancel(); 180 } 181 }); 182 mScaleAnim.addListener(new AnimatorListenerAdapter() { 183 @Override 184 public void onAnimationStart(Animator animation) { 185 mScaleAnimStarted = true; 186 } 187 188 @Override 189 public void onAnimationEnd(Animator animation) { 190 super.onAnimationEnd(animation); 191 if (mOnScaleAnimEndCallback != null) { 192 mOnScaleAnimEndCallback.run(); 193 } 194 } 195 }); 196 // Set up the shift animator. 197 mShiftAnim = ValueAnimator.ofFloat(0f, 1f); 198 mShiftAnim.addListener(new AnimatorListenerAdapter() { 199 @Override 200 public void onAnimationStart(Animator animation) { 201 mShiftAnimStarted = true; 202 } 203 204 @Override 205 public void onAnimationEnd(Animator animation) { 206 if (mOnShiftAnimEndCallback != null) { 207 mOnShiftAnimEndCallback.run(); 208 } 209 } 210 }); 211 212 setDragRegion(new Rect(0, 0, width, height)); 213 214 // The point in our scaled bitmap that the touch events are located 215 mRegistrationX = registrationX; 216 mRegistrationY = registrationY; 217 218 mInitialScale = initialScale; 219 mScaleOnDrop = scaleOnDrop; 220 221 // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass 222 measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 223 224 mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 225 setElevation(getResources().getDimension(R.dimen.drag_elevation)); 226 setWillNotDraw(false); 227 } 228 229 /** Callback invoked when the scale animation ends. */ setOnScaleAnimEndCallback(Runnable callback)230 public void setOnScaleAnimEndCallback(Runnable callback) { 231 mOnScaleAnimEndCallback = callback; 232 } 233 234 /** Callback invoked when the shift animation ends. */ setOnShiftAnimEndCallback(Runnable callback)235 public void setOnShiftAnimEndCallback(Runnable callback) { 236 mOnShiftAnimEndCallback = callback; 237 } 238 239 /** 240 * Initialize {@code #mIconDrawable} if the item can be represented using 241 * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}. 242 */ 243 @TargetApi(Build.VERSION_CODES.O) setItemInfo(final ItemInfo info)244 public void setItemInfo(final ItemInfo info) { 245 // Load the adaptive icon on a background thread and add the view in ui thread. 246 MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { 247 Object[] outObj = new Object[1]; 248 boolean[] outIsIconThemed = new boolean[1]; 249 int w = mWidth; 250 int h = mHeight; 251 Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h, 252 true /* shouldThemeIcon */, outObj, outIsIconThemed); 253 if (dr instanceof AdaptiveIconDrawable) { 254 int blurMargin = (int) mActivity.getResources() 255 .getDimension(R.dimen.blur_size_medium_outline) / 2; 256 257 Rect bounds = new Rect(0, 0, w, h); 258 bounds.inset(blurMargin, blurMargin); 259 // Badge is applied after icon normalization so the bounds for badge should not 260 // be scaled down due to icon normalization. 261 mBadge = getBadge(mActivity, info, outObj[0], outIsIconThemed[0]); 262 FastBitmapDrawable.setBadgeBounds(mBadge, bounds); 263 264 // Do not draw the background in case of folder as its translucent 265 final boolean shouldDrawBackground = !(dr instanceof FolderAdaptiveIcon); 266 267 try (LauncherIcons li = LauncherIcons.obtain(mActivity)) { 268 Drawable nDr; // drawable to be normalized 269 if (shouldDrawBackground) { 270 nDr = dr; 271 } else { 272 // Since we just want the scale, avoid heavy drawing operations 273 nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); 274 } 275 Utilities.scaleRectAboutCenter(bounds, 276 li.getNormalizer().getScale(nDr, null, null, null)); 277 } 278 AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr; 279 280 // Shrink very tiny bit so that the clip path is smaller than the original bitmap 281 // that has anti aliased edges and shadows. 282 Rect shrunkBounds = new Rect(bounds); 283 Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f); 284 adaptiveIcon.setBounds(shrunkBounds); 285 final Path mask = adaptiveIcon.getIconMask(); 286 287 mTranslateX = new SpringFloatValue(DragView.this, 288 w * AdaptiveIconDrawable.getExtraInsetFraction()); 289 mTranslateY = new SpringFloatValue(DragView.this, 290 h * AdaptiveIconDrawable.getExtraInsetFraction()); 291 292 bounds.inset( 293 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()), 294 (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction()) 295 ); 296 mBgSpringDrawable = adaptiveIcon.getBackground(); 297 if (mBgSpringDrawable == null) { 298 mBgSpringDrawable = new ColorDrawable(Color.TRANSPARENT); 299 } 300 mBgSpringDrawable.setBounds(bounds); 301 mFgSpringDrawable = adaptiveIcon.getForeground(); 302 if (mFgSpringDrawable == null) { 303 mFgSpringDrawable = new ColorDrawable(Color.TRANSPARENT); 304 } 305 mFgSpringDrawable.setBounds(bounds); 306 307 new Handler(Looper.getMainLooper()).post(() -> mOnDragStartCallback.add(() -> { 308 // TODO: Consider fade-in animation 309 // Assign the variable on the UI thread to avoid race conditions. 310 mScaledMaskPath = mask; 311 // Avoid relayout as we do not care about children affecting layout 312 removeAllViewsInLayout(); 313 314 if (info.isDisabled()) { 315 ColorFilter filter = getDisabledColorFilter(); 316 mBgSpringDrawable.setColorFilter(filter); 317 mFgSpringDrawable.setColorFilter(filter); 318 mBadge.setColorFilter(filter); 319 } 320 invalidate(); 321 })); 322 } 323 }); 324 } 325 326 /** 327 * Called when pre-drag finishes for an icon 328 */ onDragStart()329 public void onDragStart() { 330 mOnDragStartCallback.executeAllAndDestroy(); 331 } 332 333 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)334 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 335 super.onMeasure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY)); 336 } 337 getDragRegionWidth()338 public int getDragRegionWidth() { 339 return mDragRegion.width(); 340 } 341 getDragRegionHeight()342 public int getDragRegionHeight() { 343 return mDragRegion.height(); 344 } 345 setHasDragOffset(boolean hasDragOffset)346 public void setHasDragOffset(boolean hasDragOffset) { 347 mHasDragOffset = hasDragOffset; 348 } 349 getHasDragOffset()350 public boolean getHasDragOffset() { 351 return mHasDragOffset; 352 } 353 setDragRegion(Rect r)354 public void setDragRegion(Rect r) { 355 mDragRegion = r; 356 } 357 getDragRegion()358 public Rect getDragRegion() { 359 return mDragRegion; 360 } 361 362 @Override draw(Canvas canvas)363 public void draw(Canvas canvas) { 364 super.draw(canvas); 365 366 // Draw after the content 367 mHasDrawn = true; 368 if (mScaledMaskPath != null) { 369 int cnt = canvas.save(); 370 canvas.clipPath(mScaledMaskPath); 371 mBgSpringDrawable.draw(canvas); 372 canvas.translate(mTranslateX.mValue, mTranslateY.mValue); 373 mFgSpringDrawable.draw(canvas); 374 canvas.restoreToCount(cnt); 375 mBadge.draw(canvas); 376 } 377 } 378 crossFadeContent(Drawable crossFadeDrawable, int duration)379 public void crossFadeContent(Drawable crossFadeDrawable, int duration) { 380 if (mContent.getParent() == null) { 381 // If the content is already removed, ignore 382 return; 383 } 384 ImageView newContent = getViewFromDrawable(getContext(), crossFadeDrawable); 385 // We need to fill the ImageView with the content, otherwise the shapes of the final view 386 // and the drag view might not match exactly 387 newContent.setScaleType(ImageView.ScaleType.FIT_XY); 388 newContent.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY)); 389 newContent.layout(0, 0, mWidth, mHeight); 390 addViewInLayout(newContent, 0, new LayoutParams(mWidth, mHeight)); 391 392 AnimatorSet anim = new AnimatorSet(); 393 anim.play(ObjectAnimator.ofFloat(newContent, VIEW_ALPHA, 0, 1)); 394 anim.play(ObjectAnimator.ofFloat(mContent, VIEW_ALPHA, 0)); 395 anim.setDuration(duration).setInterpolator(Interpolators.DECELERATE_1_5); 396 anim.start(); 397 } 398 hasDrawn()399 public boolean hasDrawn() { 400 return mHasDrawn; 401 } 402 403 /** 404 * Create a window containing this view and show it. 405 * 406 * @param touchX the x coordinate the user touched in DragLayer coordinates 407 * @param touchY the y coordinate the user touched in DragLayer coordinates 408 */ show(int touchX, int touchY)409 public void show(int touchX, int touchY) { 410 mDragLayer.addView(this); 411 412 // Start the pick-up animation 413 BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(mWidth, mHeight); 414 lp.customPosition = true; 415 setLayoutParams(lp); 416 417 if (mContent != null) { 418 // At the drag start, the source view visibility is set to invisible. 419 if (getHasDragOffset()) { 420 // If there is any dragOffset, this means the content will show away of the original 421 // icon location, otherwise it's fine since original content would just show at the 422 // same spot. 423 mContent.setVisibility(INVISIBLE); 424 } else { 425 mContent.setVisibility(VISIBLE); 426 } 427 } 428 429 move(touchX, touchY); 430 // Post the animation to skip other expensive work happening on the first frame 431 post(mScaleAnim::start); 432 } 433 cancelAnimation()434 public void cancelAnimation() { 435 if (mScaleAnim != null && mScaleAnim.isRunning()) { 436 mScaleAnim.cancel(); 437 } 438 } 439 440 /** {@code true} if the scale animation has finished. */ isScaleAnimationFinished()441 public boolean isScaleAnimationFinished() { 442 return mScaleAnimStarted && !mScaleAnim.isRunning(); 443 } 444 445 /** {@code true} if the shift animation has finished. */ isShiftAnimationFinished()446 public boolean isShiftAnimationFinished() { 447 return mShiftAnimStarted && !mShiftAnim.isRunning(); 448 } 449 450 /** 451 * Move the window containing this view. 452 * 453 * @param touchX the x coordinate the user touched in DragLayer coordinates 454 * @param touchY the y coordinate the user touched in DragLayer coordinates 455 */ move(int touchX, int touchY)456 public void move(int touchX, int touchY) { 457 if (touchX > 0 && touchY > 0 && mLastTouchX > 0 && mLastTouchY > 0 458 && mScaledMaskPath != null) { 459 mTranslateX.animateToPos(mLastTouchX - touchX); 460 mTranslateY.animateToPos(mLastTouchY - touchY); 461 } 462 mLastTouchX = touchX; 463 mLastTouchY = touchY; 464 applyTranslation(); 465 } 466 467 /** 468 * Animate this DragView to the given DragLayer coordinates and then remove it. 469 */ animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration)470 public abstract void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, 471 int duration); 472 animateShift(final int shiftX, final int shiftY)473 public void animateShift(final int shiftX, final int shiftY) { 474 if (mShiftAnim.isStarted()) return; 475 476 // Set mContent visibility to visible to show icon regardless in case it is INVISIBLE. 477 if (mContent != null) mContent.setVisibility(VISIBLE); 478 479 mAnimatedShiftX = shiftX; 480 mAnimatedShiftY = shiftY; 481 applyTranslation(); 482 mShiftAnim.addUpdateListener(new AnimatorUpdateListener() { 483 @Override 484 public void onAnimationUpdate(ValueAnimator animation) { 485 float fraction = 1 - animation.getAnimatedFraction(); 486 mAnimatedShiftX = (int) (fraction * shiftX); 487 mAnimatedShiftY = (int) (fraction * shiftY); 488 applyTranslation(); 489 } 490 }); 491 mShiftAnim.start(); 492 } 493 applyTranslation()494 private void applyTranslation() { 495 setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX); 496 setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY); 497 } 498 499 /** 500 * Detaches {@link #mContent}, if previously attached, from this view. 501 * 502 * <p>In the case of no change in the drop position, sets {@code reattachToPreviousParent} to 503 * {@code true} to attach the {@link #mContent} back to its previous parent. 504 */ detachContentView(boolean reattachToPreviousParent)505 public void detachContentView(boolean reattachToPreviousParent) { 506 if (mContent != null && mContentViewParent != null && mContentViewInParentViewIndex >= 0) { 507 Picture picture = new Picture(); 508 mContent.draw(picture.beginRecording(mWidth, mHeight)); 509 picture.endRecording(); 510 View view = new View(mActivity); 511 view.setBackground(new PictureDrawable(picture)); 512 view.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY)); 513 view.layout(mContent.getLeft(), mContent.getTop(), 514 mContent.getRight(), mContent.getBottom()); 515 setClipToOutline(mContent.getClipToOutline()); 516 setOutlineProvider(mContent.getOutlineProvider()); 517 addViewInLayout(view, indexOfChild(mContent), mContent.getLayoutParams(), true); 518 519 removeViewInLayout(mContent); 520 mContent.setVisibility(INVISIBLE); 521 mContent.setLayoutParams(mContentViewLayoutParams); 522 if (reattachToPreviousParent) { 523 mContentViewParent.addView(mContent, mContentViewInParentViewIndex); 524 } 525 mContentViewParent = null; 526 mContentViewInParentViewIndex = -1; 527 } 528 } 529 530 /** 531 * Removes this view from the {@link DragLayer}. 532 * 533 * <p>If the drag content is a {@link #mContent}, this call doesn't reattach the 534 * {@link #mContent} back to its previous parent. To reattach to previous parent, the caller 535 * should call {@link #detachContentView} with {@code reattachToPreviousParent} sets to true 536 * before this call. 537 */ remove()538 public void remove() { 539 if (getParent() != null) { 540 mDragLayer.removeView(DragView.this); 541 } 542 } 543 getBlurSizeOutline()544 public int getBlurSizeOutline() { 545 return mBlurSizeOutline; 546 } 547 getInitialScale()548 public float getInitialScale() { 549 return mInitialScale; 550 } 551 getEndScale()552 public float getEndScale() { 553 return mEndScale; 554 } 555 556 @Override hasOverlappingRendering()557 public boolean hasOverlappingRendering() { 558 return false; 559 } 560 561 /** Returns the current content view that is rendered in the drag view. */ getContentView()562 public View getContentView() { 563 return mContent; 564 } 565 566 /** 567 * Returns the previous {@link ViewGroup} parent of the {@link #mContent} before the drag 568 * content is attached to this view. 569 */ 570 @Nullable getContentViewParent()571 public ViewGroup getContentViewParent() { 572 return mContentViewParent; 573 } 574 575 private static class SpringFloatValue { 576 577 private static final FloatPropertyCompat<SpringFloatValue> VALUE = 578 new FloatPropertyCompat<SpringFloatValue>("value") { 579 @Override 580 public float getValue(SpringFloatValue object) { 581 return object.mValue; 582 } 583 584 @Override 585 public void setValue(SpringFloatValue object, float value) { 586 object.mValue = value; 587 object.mView.invalidate(); 588 } 589 }; 590 591 // Following three values are fine tuned with motion ux designer 592 private static final int STIFFNESS = 4000; 593 private static final float DAMPENING_RATIO = 1f; 594 private static final int PARALLAX_MAX_IN_DP = 8; 595 596 private final View mView; 597 private final SpringAnimation mSpring; 598 private final float mDelta; 599 600 private float mValue; 601 SpringFloatValue(View view, float range)602 public SpringFloatValue(View view, float range) { 603 mView = view; 604 mSpring = new SpringAnimation(this, VALUE, 0) 605 .setMinValue(-range).setMaxValue(range) 606 .setSpring(new SpringForce(0) 607 .setDampingRatio(DAMPENING_RATIO) 608 .setStiffness(STIFFNESS)); 609 mDelta = Math.min( 610 range, view.getResources().getDisplayMetrics().density * PARALLAX_MAX_IN_DP); 611 } 612 animateToPos(float value)613 public void animateToPos(float value) { 614 mSpring.animateToFinalPosition(Utilities.boundToRange(value, -mDelta, mDelta)); 615 } 616 } 617 getViewFromDrawable(Context context, Drawable drawable)618 private static ImageView getViewFromDrawable(Context context, Drawable drawable) { 619 ImageView iv = new ImageView(context); 620 iv.setImageDrawable(drawable); 621 return iv; 622 } 623 624 /** 625 * Removes any stray DragView from the DragLayer. 626 */ removeAllViews(ActivityContext activity)627 public static void removeAllViews(ActivityContext activity) { 628 BaseDragLayer dragLayer = activity.getDragLayer(); 629 // Iterate in reverse order. DragView is added later to the dragLayer, 630 // and will be one of the last views. 631 for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { 632 View child = dragLayer.getChildAt(i); 633 if (child instanceof DragView) { 634 dragLayer.removeView(child); 635 } 636 } 637 } 638 } 639