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