summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/util/FlingAnimation.java
blob: da8bae712d8ea2a6c585641680fd9a31479a6016 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.android.launcher3.util;

import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.animation.DecelerateInterpolator;

import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;

public class FlingAnimation implements AnimatorUpdateListener {

    /**
     * Maximum acceleration in one dimension (pixels per milliseconds)
     */
    private static final float MAX_ACCELERATION = 0.5f;
    private static final int DRAG_END_DELAY = 300;

    protected final DragObject mDragObject;
    protected final Rect mIconRect;
    protected final DragLayer mDragLayer;
    protected final Rect mFrom;
    protected final int mDuration;
    protected final float mUX, mUY;
    protected final float mAnimationTimeFraction;
    protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);

    protected float mAX, mAY;

    /**
     * @param vel initial fling velocity in pixels per second.
     */
    public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
        mDragObject = d;
        mUX = vel.x / 1000;
        mUY = vel.y / 1000;
        mIconRect = iconRect;

        mDragLayer = dragLayer;
        mFrom = new Rect();
        dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);

        float scale = d.dragView.getScaleX();
        float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
        float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
        mFrom.left += xOffset;
        mFrom.right -= xOffset;
        mFrom.top += yOffset;
        mFrom.bottom -= yOffset;

        mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
        mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
    }

    /**
     * The fling animation is based on the following system
     *   - Apply a constant force in the y direction to causing the fling to decelerate.
     *   - The animation runs for the time taken by the object to go out of the screen.
     *   - Calculate a constant acceleration in x direction such that the object reaches
     *     {@link #mIconRect} in the given time.
     */
    protected int initFlingUpDuration() {
        float sY = -mFrom.bottom;

        float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
        if (d >= 0) {
            // sY can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for y direction.
            mAY = MAX_ACCELERATION;
        } else {
            // sY is not reachable, decrease the acceleration so that sY is almost reached.
            d = 0;
            mAY = mUY * mUY / (2 * -sY);
        }
        double t = (-mUY - Math.sqrt(d)) / mAY;

        float sX = -mFrom.exactCenterX() + mIconRect.exactCenterX();

        // Find horizontal acceleration such that: u*t + a*t*t/2 = s
        mAX = (float) ((sX - t * mUX) * 2 / (t * t));
        return (int) Math.round(t);
    }

    /**
     * The fling animation is based on the following system
     *   - Apply a constant force in the x direction to causing the fling to decelerate.
     *   - The animation runs for the time taken by the object to go out of the screen.
     *   - Calculate a constant acceleration in y direction such that the object reaches
     *     {@link #mIconRect} in the given time.
     */
    protected int initFlingLeftDuration() {
        float sX = -mFrom.right;

        float d = mUX * mUX + 2 * sX * MAX_ACCELERATION;
        if (d >= 0) {
            // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction.
            mAX = MAX_ACCELERATION;
        } else {
            // sX is not reachable, decrease the acceleration so that sX is almost reached.
            d = 0;
            mAX = mUX * mUX / (2 * -sX);
        }
        double t = (-mUX - Math.sqrt(d)) / mAX;

        float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();

        // Find vertical acceleration such that: u*t + a*t*t/2 = s
        mAY = (float) ((sY - t * mUY) * 2 / (t * t));
        return (int) Math.round(t);
    }

    public final int getDuration() {
        return mDuration + DRAG_END_DELAY;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float t = animation.getAnimatedFraction();
        if (t > mAnimationTimeFraction) {
            t = 1;
        } else {
            t = t / mAnimationTimeFraction;
        }
        final DragView dragView = (DragView) mDragLayer.getAnimatedView();
        final float time = t * mDuration;
        dragView.setTranslationX(time * mUX + mFrom.left + mAX * time * time / 2);
        dragView.setTranslationY(time * mUY + mFrom.top + mAY * time * time / 2);
        dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
    }
}