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