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