VelocityTracker.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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;
21
22/**
23 * Helper for tracking the velocity of touch events, for implementing
24 * flinging and other such gestures.  Use {@link #obtain} to retrieve a
25 * new instance of the class when you are going to begin tracking, put
26 * the motion events you receive into it with {@link #addMovement(MotionEvent)},
27 * and when you want to determine the velocity call
28 * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
29 * and {@link #getXVelocity()}.
30 */
31public final class VelocityTracker {
32    static final String TAG = "VelocityTracker";
33    static final boolean DEBUG = false;
34    static final boolean localLOGV = DEBUG || Config.LOGV;
35
36    static final int NUM_PAST = 10;
37    static final int LONGEST_PAST_TIME = 200;
38
39    static final VelocityTracker[] mPool = new VelocityTracker[1];
40
41    final float mPastX[] = new float[NUM_PAST];
42    final float mPastY[] = new float[NUM_PAST];
43    final long mPastTime[] = new long[NUM_PAST];
44
45    float mYVelocity;
46    float mXVelocity;
47
48    /**
49     * Retrieve a new VelocityTracker object to watch the velocity of a
50     * motion.  Be sure to call {@link #recycle} when done.  You should
51     * generally only maintain an active object while tracking a movement,
52     * so that the VelocityTracker can be re-used elsewhere.
53     *
54     * @return Returns a new VelocityTracker.
55     */
56    static public VelocityTracker obtain() {
57        synchronized (mPool) {
58            VelocityTracker vt = mPool[0];
59            if (vt != null) {
60                vt.clear();
61                return vt;
62            }
63            return new VelocityTracker();
64        }
65    }
66
67    /**
68     * Return a VelocityTracker object back to be re-used by others.  You must
69     * not touch the object after calling this function.
70     */
71    public void recycle() {
72        synchronized (mPool) {
73            mPool[0] = this;
74        }
75    }
76
77    private VelocityTracker() {
78    }
79
80    /**
81     * Reset the velocity tracker back to its initial state.
82     */
83    public void clear() {
84        mPastTime[0] = 0;
85    }
86
87    /**
88     * Add a user's movement to the tracker.  You should call this for the
89     * initial {@link MotionEvent#ACTION_DOWN}, the following
90     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
91     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
92     * for whichever events you desire.
93     *
94     * @param ev The MotionEvent you received and would like to track.
95     */
96    public void addMovement(MotionEvent ev) {
97        long time = ev.getEventTime();
98        final int N = ev.getHistorySize();
99        for (int i=0; i<N; i++) {
100            addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
101                    ev.getHistoricalEventTime(i));
102        }
103        addPoint(ev.getX(), ev.getY(), time);
104    }
105
106    private void addPoint(float x, float y, long time) {
107        int drop = -1;
108        int i;
109        if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
110        final long[] pastTime = mPastTime;
111        for (i=0; i<NUM_PAST; i++) {
112            if (pastTime[i] == 0) {
113                break;
114            } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
115                if (localLOGV) Log.v(TAG, "Dropping past too old at "
116                        + i + " time=" + pastTime[i]);
117                drop = i;
118            }
119        }
120        if (localLOGV) Log.v(TAG, "Add index: " + i);
121        if (i == NUM_PAST && drop < 0) {
122            drop = 0;
123        }
124        if (drop == i) drop--;
125        final float[] pastX = mPastX;
126        final float[] pastY = mPastY;
127        if (drop >= 0) {
128            if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
129            final int start = drop+1;
130            final int count = NUM_PAST-drop-1;
131            System.arraycopy(pastX, start, pastX, 0, count);
132            System.arraycopy(pastY, start, pastY, 0, count);
133            System.arraycopy(pastTime, start, pastTime, 0, count);
134            i -= (drop+1);
135        }
136        pastX[i] = x;
137        pastY[i] = y;
138        pastTime[i] = time;
139        i++;
140        if (i < NUM_PAST) {
141            pastTime[i] = 0;
142        }
143    }
144
145    /**
146     * Compute the current velocity based on the points that have been
147     * collected.  Only call this when you actually want to retrieve velocity
148     * information, as it is relatively expensive.  You can then retrieve
149     * the velocity with {@link #getXVelocity()} and
150     * {@link #getYVelocity()}.
151     *
152     * @param units The units you would like the velocity in.  A value of 1
153     * provides pixels per millisecond, 1000 provides pixels per second, etc.
154     */
155    public void computeCurrentVelocity(int units) {
156        final float[] pastX = mPastX;
157        final float[] pastY = mPastY;
158        final long[] pastTime = mPastTime;
159
160        // Kind-of stupid.
161        final float oldestX = pastX[0];
162        final float oldestY = pastY[0];
163        final long oldestTime = pastTime[0];
164        float accumX = 0;
165        float accumY = 0;
166        int N=0;
167        while (N < NUM_PAST) {
168            if (pastTime[N] == 0) {
169                break;
170            }
171            N++;
172        }
173        // Skip the last received event, since it is probably pretty noisy.
174        if (N > 3) N--;
175
176        for (int i=1; i < N; i++) {
177            final int dur = (int)(pastTime[i] - oldestTime);
178            if (dur == 0) continue;
179            float dist = pastX[i] - oldestX;
180            float vel = (dist/dur) * units;   // pixels/frame.
181            if (accumX == 0) accumX = vel;
182            else accumX = (accumX + vel) * .5f;
183
184            dist = pastY[i] - oldestY;
185            vel = (dist/dur) * units;   // pixels/frame.
186            if (accumY == 0) accumY = vel;
187            else accumY = (accumY + vel) * .5f;
188        }
189        mXVelocity = accumX;
190        mYVelocity = accumY;
191
192        if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
193                + mXVelocity + " N=" + N);
194    }
195
196    /**
197     * Retrieve the last computed X velocity.  You must first call
198     * {@link #computeCurrentVelocity(int)} before calling this function.
199     *
200     * @return The previously computed X velocity.
201     */
202    public float getXVelocity() {
203        return mXVelocity;
204    }
205
206    /**
207     * Retrieve the last computed Y velocity.  You must first call
208     * {@link #computeCurrentVelocity(int)} before calling this function.
209     *
210     * @return The previously computed Y velocity.
211     */
212    public float getYVelocity() {
213        return mYVelocity;
214    }
215}
216