1 package com.android.launcher3.util; 2 3 import static com.android.launcher3.LauncherState.NORMAL; 4 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 5 6 import android.animation.TimeInterpolator; 7 import android.animation.ValueAnimator; 8 import android.animation.ValueAnimator.AnimatorUpdateListener; 9 import android.graphics.PointF; 10 import android.graphics.Rect; 11 import android.graphics.RectF; 12 import android.view.animation.AnimationUtils; 13 import android.view.animation.DecelerateInterpolator; 14 15 import com.android.launcher3.ButtonDropTarget; 16 import com.android.launcher3.DropTarget.DragObject; 17 import com.android.launcher3.Launcher; 18 import com.android.launcher3.dragndrop.DragLayer; 19 import com.android.launcher3.dragndrop.DragOptions; 20 import com.android.launcher3.dragndrop.DragView; 21 22 public class FlingAnimation implements AnimatorUpdateListener, Runnable { 23 24 /** 25 * Maximum acceleration in one dimension (pixels per milliseconds) 26 */ 27 private static final float MAX_ACCELERATION = 0.5f; 28 private static final int DRAG_END_DELAY = 300; 29 30 private final ButtonDropTarget mDropTarget; 31 private final Launcher mLauncher; 32 33 protected final DragObject mDragObject; 34 protected final DragOptions mDragOptions; 35 protected final DragLayer mDragLayer; 36 protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f); 37 protected final float mUX, mUY; 38 39 protected Rect mIconRect; 40 protected RectF mFrom; 41 protected int mDuration; 42 protected float mAnimationTimeFraction; 43 44 protected float mAX, mAY; 45 FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher, DragOptions options)46 public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher, 47 DragOptions options) { 48 mDropTarget = dropTarget; 49 mLauncher = launcher; 50 mDragObject = d; 51 mUX = vel.x / 1000; 52 mUY = vel.y / 1000; 53 mDragLayer = mLauncher.getDragLayer(); 54 mDragOptions = options; 55 } 56 57 @Override run()58 public void run() { 59 mIconRect = mDropTarget.getIconRect(mDragObject); 60 mDragObject.dragView.cancelAnimation(); 61 mDragObject.dragView.requestLayout(); 62 63 // Initiate from 64 Rect from = new Rect(); 65 mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, from); 66 67 mFrom = new RectF(from); 68 mFrom.inset( 69 ((1 - mDragObject.dragView.getScaleX()) * from.width()) / 2f, 70 ((1 - mDragObject.dragView.getScaleY()) * from.height()) / 2f); 71 mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration(); 72 73 mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY); 74 75 final int duration = mDuration + DRAG_END_DELAY; 76 final long startTime = AnimationUtils.currentAnimationTimeMillis(); 77 78 // NOTE: Because it takes time for the first frame of animation to actually be 79 // called and we expect the animation to be a continuation of the fling, we have 80 // to account for the time that has elapsed since the fling finished. And since 81 // we don't have a startDelay, we will always get call to update when we call 82 // start() (which we want to ignore). 83 final TimeInterpolator tInterpolator = new TimeInterpolator() { 84 private int mCount = -1; 85 private float mOffset = 0f; 86 87 @Override 88 public float getInterpolation(float t) { 89 if (mCount < 0) { 90 mCount++; 91 } else if (mCount == 0) { 92 mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() - 93 startTime) / duration); 94 mCount++; 95 } 96 return Math.min(1f, mOffset + t); 97 } 98 }; 99 100 mDropTarget.onDrop(mDragObject, mDragOptions); 101 ValueAnimator anim = ValueAnimator.ofFloat(0, 1); 102 anim.setDuration(duration).setInterpolator(tInterpolator); 103 anim.addUpdateListener(this); 104 anim.addListener(forEndCallback(() -> { 105 mLauncher.getStateManager().goToState(NORMAL); 106 mDropTarget.completeDrop(mDragObject); 107 })); 108 mDragLayer.playDropAnimation(mDragObject.dragView, anim, DragLayer.ANIMATION_END_DISAPPEAR); 109 } 110 111 /** 112 * The fling animation is based on the following system 113 * - Apply a constant force in the y direction to causing the fling to decelerate. 114 * - The animation runs for the time taken by the object to go out of the screen. 115 * - Calculate a constant acceleration in x direction such that the object reaches 116 * {@link #mIconRect} in the given time. 117 */ initFlingUpDuration()118 protected int initFlingUpDuration() { 119 float sY = -mFrom.bottom; 120 121 float d = mUY * mUY + 2 * sY * MAX_ACCELERATION; 122 if (d >= 0) { 123 // sY can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for y direction. 124 mAY = MAX_ACCELERATION; 125 } else { 126 // sY is not reachable, decrease the acceleration so that sY is almost reached. 127 d = 0; 128 mAY = mUY * mUY / (2 * -sY); 129 } 130 double t = (-mUY - Math.sqrt(d)) / mAY; 131 132 float sX = -mFrom.centerX() + mIconRect.exactCenterX(); 133 134 // Find horizontal acceleration such that: u*t + a*t*t/2 = s 135 mAX = (float) ((sX - t * mUX) * 2 / (t * t)); 136 return (int) Math.round(t); 137 } 138 139 /** 140 * The fling animation is based on the following system 141 * - Apply a constant force in the x direction to causing the fling to decelerate. 142 * - The animation runs for the time taken by the object to go out of the screen. 143 * - Calculate a constant acceleration in y direction such that the object reaches 144 * {@link #mIconRect} in the given time. 145 */ initFlingLeftDuration()146 protected int initFlingLeftDuration() { 147 float sX = -mFrom.right; 148 149 float d = mUX * mUX + 2 * sX * MAX_ACCELERATION; 150 if (d >= 0) { 151 // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction. 152 mAX = MAX_ACCELERATION; 153 } else { 154 // sX is not reachable, decrease the acceleration so that sX is almost reached. 155 d = 0; 156 mAX = mUX * mUX / (2 * -sX); 157 } 158 double t = (-mUX - Math.sqrt(d)) / mAX; 159 160 float sY = -mFrom.centerY() + mIconRect.exactCenterY(); 161 162 // Find vertical acceleration such that: u*t + a*t*t/2 = s 163 mAY = (float) ((sY - t * mUY) * 2 / (t * t)); 164 return (int) Math.round(t); 165 } 166 167 @Override onAnimationUpdate(ValueAnimator animation)168 public void onAnimationUpdate(ValueAnimator animation) { 169 float t = animation.getAnimatedFraction(); 170 if (t > mAnimationTimeFraction) { 171 t = 1; 172 } else { 173 t = t / mAnimationTimeFraction; 174 } 175 final DragView dragView = (DragView) mDragLayer.getAnimatedView(); 176 final float time = t * mDuration; 177 dragView.setTranslationX(time * mUX + mFrom.left + mAX * time * time / 2); 178 dragView.setTranslationY(time * mUY + mFrom.top + mAY * time * time / 2); 179 dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t)); 180 } 181 } 182