1package com.android.launcher3.util;
2
3import android.animation.TimeInterpolator;
4import android.animation.ValueAnimator;
5import android.animation.ValueAnimator.AnimatorUpdateListener;
6import android.graphics.PointF;
7import android.graphics.Rect;
8import android.view.animation.DecelerateInterpolator;
9
10import com.android.launcher3.DragLayer;
11import com.android.launcher3.DragView;
12import com.android.launcher3.DropTarget.DragObject;
13
14public class FlingAnimation implements AnimatorUpdateListener {
15
16    /**
17     * Maximum acceleration in one dimension (pixels per milliseconds)
18     */
19    private static final float MAX_ACCELERATION = 0.5f;
20    private static final int DRAG_END_DELAY = 300;
21
22    protected final DragObject mDragObject;
23    protected final Rect mIconRect;
24    protected final DragLayer mDragLayer;
25    protected final Rect mFrom;
26    protected final int mDuration;
27    protected final float mUX, mUY;
28    protected final float mAnimationTimeFraction;
29    protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
30
31    protected float mAX, mAY;
32
33    /**
34     * @param vel initial fling velocity in pixels per second.
35     */
36    public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
37        mDragObject = d;
38        mUX = vel.x / 1000;
39        mUY = vel.y / 1000;
40        mIconRect = iconRect;
41
42        mDragLayer = dragLayer;
43        mFrom = new Rect();
44        dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);
45
46        float scale = d.dragView.getScaleX();
47        float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
48        float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
49        mFrom.left += xOffset;
50        mFrom.right -= xOffset;
51        mFrom.top += yOffset;
52        mFrom.bottom -= yOffset;
53
54        mDuration = initDuration();
55        mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
56    }
57
58    /**
59     * The fling animation is based on the following system
60     *   - Apply a constant force in the y direction to causing the fling to decelerate.
61     *   - The animation runs for the time taken by the object to go out of the screen.
62     *   - Calculate a constant acceleration in x direction such that the object reaches
63     *     {@link #mIconRect} in the given time.
64     */
65    protected int initDuration() {
66        float sY = -mFrom.bottom;
67
68        float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
69        if (d >= 0) {
70            // sY can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for y direction.
71            mAY = MAX_ACCELERATION;
72        } else {
73            // sY is not reachable, decrease the acceleration so that sY is almost reached.
74            d = 0;
75            mAY = mUY * mUY / (2 * -sY);
76        }
77        double t = (-mUY - Math.sqrt(d)) / mAY;
78
79        float sX = -mFrom.exactCenterX() + mIconRect.exactCenterX();
80
81        // Find horizontal acceleration such that: u*t + a*t*t/2 = s
82        mAX = (float) ((sX - t * mUX) * 2 / (t * t));
83        return (int) Math.round(t);
84    }
85
86    public final int getDuration() {
87        return mDuration + DRAG_END_DELAY;
88    }
89
90    @Override
91    public void onAnimationUpdate(ValueAnimator animation) {
92        float t = animation.getAnimatedFraction();
93        if (t > mAnimationTimeFraction) {
94            t = 1;
95        } else {
96            t = t / mAnimationTimeFraction;
97        }
98        final DragView dragView = (DragView) mDragLayer.getAnimatedView();
99        final float time = t * mDuration;
100        dragView.setTranslationX(time * mUX + mFrom.left + mAX * time * time / 2);
101        dragView.setTranslationY(time * mUY + mFrom.top + mAY * time * time / 2);
102        dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
103    }
104}
105