VelocityTracker.java revision 2e9bbce84d9697a9dcccd02cec55dc485d985746
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.util.Config;
20import android.util.Log;
21import android.util.Poolable;
22import android.util.Pool;
23import android.util.Pools;
24import android.util.PoolableManager;
25
26/**
27 * Helper for tracking the velocity of touch events, for implementing
28 * flinging and other such gestures.  Use {@link #obtain} to retrieve a
29 * new instance of the class when you are going to begin tracking, put
30 * the motion events you receive into it with {@link #addMovement(MotionEvent)},
31 * and when you want to determine the velocity call
32 * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
33 * and {@link #getXVelocity()}.
34 */
35public final class VelocityTracker implements Poolable<VelocityTracker> {
36    static final String TAG = "VelocityTracker";
37    static final boolean DEBUG = false;
38    static final boolean localLOGV = DEBUG || Config.LOGV;
39
40    static final int NUM_PAST = 10;
41    static final int LONGEST_PAST_TIME = 200;
42
43    static final VelocityTracker[] mPool = new VelocityTracker[1];
44    private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
45            Pools.finitePool(new PoolableManager<VelocityTracker>() {
46                public VelocityTracker newInstance() {
47                    return new VelocityTracker();
48                }
49
50                public void onAcquired(VelocityTracker element) {
51                    element.clear();
52                }
53
54                public void onReleased(VelocityTracker element) {
55                }
56            }, 2));
57
58    final float mPastX[] = new float[NUM_PAST];
59    final float mPastY[] = new float[NUM_PAST];
60    final long mPastTime[] = new long[NUM_PAST];
61
62    float mYVelocity;
63    float mXVelocity;
64
65    private VelocityTracker mNext;
66
67    /**
68     * Retrieve a new VelocityTracker object to watch the velocity of a
69     * motion.  Be sure to call {@link #recycle} when done.  You should
70     * generally only maintain an active object while tracking a movement,
71     * so that the VelocityTracker can be re-used elsewhere.
72     *
73     * @return Returns a new VelocityTracker.
74     */
75    static public VelocityTracker obtain() {
76        return sPool.acquire();
77    }
78
79    /**
80     * Return a VelocityTracker object back to be re-used by others.  You must
81     * not touch the object after calling this function.
82     */
83    public void recycle() {
84        sPool.release(this);
85    }
86
87    /**
88     * @hide
89     */
90    public void setNextPoolable(VelocityTracker element) {
91        mNext = element;
92    }
93
94    /**
95     * @hide
96     */
97    public VelocityTracker getNextPoolable() {
98        return mNext;
99    }
100
101    private VelocityTracker() {
102    }
103
104    /**
105     * Reset the velocity tracker back to its initial state.
106     */
107    public void clear() {
108        mPastTime[0] = 0;
109    }
110
111    /**
112     * Add a user's movement to the tracker.  You should call this for the
113     * initial {@link MotionEvent#ACTION_DOWN}, the following
114     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
115     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
116     * for whichever events you desire.
117     *
118     * @param ev The MotionEvent you received and would like to track.
119     */
120    public void addMovement(MotionEvent ev) {
121        long time = ev.getEventTime();
122        final int N = ev.getHistorySize();
123        for (int i=0; i<N; i++) {
124            addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
125                    ev.getHistoricalEventTime(i));
126        }
127        addPoint(ev.getX(), ev.getY(), time);
128    }
129
130    private void addPoint(float x, float y, long time) {
131        int drop = -1;
132        int i;
133        if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
134        final long[] pastTime = mPastTime;
135        for (i=0; i<NUM_PAST; i++) {
136            if (pastTime[i] == 0) {
137                break;
138            } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
139                if (localLOGV) Log.v(TAG, "Dropping past too old at "
140                        + i + " time=" + pastTime[i]);
141                drop = i;
142            }
143        }
144        if (localLOGV) Log.v(TAG, "Add index: " + i);
145        if (i == NUM_PAST && drop < 0) {
146            drop = 0;
147        }
148        if (drop == i) drop--;
149        final float[] pastX = mPastX;
150        final float[] pastY = mPastY;
151        if (drop >= 0) {
152            if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
153            final int start = drop+1;
154            final int count = NUM_PAST-drop-1;
155            System.arraycopy(pastX, start, pastX, 0, count);
156            System.arraycopy(pastY, start, pastY, 0, count);
157            System.arraycopy(pastTime, start, pastTime, 0, count);
158            i -= (drop+1);
159        }
160        pastX[i] = x;
161        pastY[i] = y;
162        pastTime[i] = time;
163        i++;
164        if (i < NUM_PAST) {
165            pastTime[i] = 0;
166        }
167    }
168
169    /**
170     * Compute the current velocity based on the points that have been
171     * collected.  Only call this when you actually want to retrieve velocity
172     * information, as it is relatively expensive.  You can then retrieve
173     * the velocity with {@link #getXVelocity()} and
174     * {@link #getYVelocity()}.
175     *
176     * @param units The units you would like the velocity in.  A value of 1
177     * provides pixels per millisecond, 1000 provides pixels per second, etc.
178     */
179    public void computeCurrentVelocity(int units) {
180        final float[] pastX = mPastX;
181        final float[] pastY = mPastY;
182        final long[] pastTime = mPastTime;
183
184        // Kind-of stupid.
185        final float oldestX = pastX[0];
186        final float oldestY = pastY[0];
187        final long oldestTime = pastTime[0];
188        float accumX = 0;
189        float accumY = 0;
190        int N=0;
191        while (N < NUM_PAST) {
192            if (pastTime[N] == 0) {
193                break;
194            }
195            N++;
196        }
197        // Skip the last received event, since it is probably pretty noisy.
198        if (N > 3) N--;
199
200        for (int i=1; i < N; i++) {
201            final int dur = (int)(pastTime[i] - oldestTime);
202            if (dur == 0) continue;
203            float dist = pastX[i] - oldestX;
204            float vel = (dist/dur) * units;   // pixels/frame.
205            if (accumX == 0) accumX = vel;
206            else accumX = (accumX + vel) * .5f;
207
208            dist = pastY[i] - oldestY;
209            vel = (dist/dur) * units;   // pixels/frame.
210            if (accumY == 0) accumY = vel;
211            else accumY = (accumY + vel) * .5f;
212        }
213        mXVelocity = accumX;
214        mYVelocity = accumY;
215
216        if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
217                + mXVelocity + " N=" + N);
218    }
219
220    /**
221     * Retrieve the last computed X velocity.  You must first call
222     * {@link #computeCurrentVelocity(int)} before calling this function.
223     *
224     * @return The previously computed X velocity.
225     */
226    public float getXVelocity() {
227        return mXVelocity;
228    }
229
230    /**
231     * Retrieve the last computed Y velocity.  You must first call
232     * {@link #computeCurrentVelocity(int)} before calling this function.
233     *
234     * @return The previously computed Y velocity.
235     */
236    public float getYVelocity() {
237        return mYVelocity;
238    }
239}
240