summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/util/FlingAnimation.java
blob: d475ee9dfbd02f6e6f81468132d4223d4a295136 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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.AnimationUtils;
import android.view.animation.DecelerateInterpolator;

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

public class FlingAnimation implements AnimatorUpdateListener, Runnable {

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

    private final ButtonDropTarget mDropTarget;
    private final Launcher mLauncher;

    protected final DragObject mDragObject;
    protected final DragLayer mDragLayer;
    protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
    protected final float mUX, mUY;

    protected Rect mIconRect;
    protected Rect mFrom;
    protected int mDuration;
    protected float mAnimationTimeFraction;

    protected float mAX, mAY;

    public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher) {
        mDropTarget = dropTarget;
        mLauncher = launcher;
        mDragObject = d;
        mUX = vel.x / 1000;
        mUY = vel.y / 1000;
        mDragLayer = mLauncher.getDragLayer();
    }

    @Override
    public void run() {
        mIconRect = mDropTarget.getIconRect(mDragObject);

        // Initiate from
        mFrom = new Rect();
        mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
        float scale = mDragObject.dragView.getScaleX();
        float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
        float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
        mFrom.left += xOffset;
        mFrom.right -= xOffset;
        mFrom.top += yOffset;
        mFrom.bottom -= yOffset;
        mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();

        mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);

        // Don't highlight the icon as it's animating
        mDragObject.dragView.setColor(0);

        final int duration = mDuration + DRAG_END_DELAY;
        final long startTime = AnimationUtils.currentAnimationTimeMillis();

        // NOTE: Because it takes time for the first frame of animation to actually be
        // called and we expect the animation to be a continuation of the fling, we have
        // to account for the time that has elapsed since the fling finished.  And since
        // we don't have a startDelay, we will always get call to update when we call
        // start() (which we want to ignore).
        final TimeInterpolator tInterpolator = new TimeInterpolator() {
            private int mCount = -1;
            private float mOffset = 0f;

            @Override
            public float getInterpolation(float t) {
                if (mCount < 0) {
                    mCount++;
                } else if (mCount == 0) {
                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
                            startTime) / duration);
                    mCount++;
                }
                return Math.min(1f, mOffset + t);
            }
        };

        Runnable onAnimationEndRunnable = new Runnable() {
            @Override
            public void run() {
                mLauncher.exitSpringLoadedDragMode();
                mDropTarget.completeDrop(mDragObject);
            }
        };

        mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
                onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
    }

    /**
     * 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);
    }

    @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));
    }
}