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