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