• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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