VelocityTracker.java revision 8acdb201bdad2cd03c07ebad9cda29f7971ed164
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[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
59    final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
60    final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
61
62    float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
63    float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
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        for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
109            mPastTime[i][0] = 0;
110        }
111    }
112
113    /**
114     * Add a user's movement to the tracker.  You should call this for the
115     * initial {@link MotionEvent#ACTION_DOWN}, the following
116     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
117     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
118     * for whichever events you desire.
119     *
120     * @param ev The MotionEvent you received and would like to track.
121     */
122    public void addMovement(MotionEvent ev) {
123        long time = ev.getEventTime();
124        final int N = ev.getHistorySize();
125        final int pointerCount = ev.getPointerCount();
126        for (int p = 0; p < pointerCount; p++) {
127            for (int i=0; i<N; i++) {
128                addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
129                        ev.getHistoricalEventTime(i));
130            }
131            addPoint(p, ev.getX(p), ev.getY(p), time);
132        }
133    }
134
135    private void addPoint(int pos, float x, float y, long time) {
136        int drop = -1;
137        int i;
138        if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
139        final long[] pastTime = mPastTime[pos];
140        for (i=0; i<NUM_PAST; i++) {
141            if (pastTime[i] == 0) {
142                break;
143            } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
144                if (localLOGV) Log.v(TAG, "Dropping past too old at "
145                        + i + " time=" + pastTime[i]);
146                drop = i;
147            }
148        }
149        if (localLOGV) Log.v(TAG, "Add index: " + i);
150        if (i == NUM_PAST && drop < 0) {
151            drop = 0;
152        }
153        if (drop == i) drop--;
154        final float[] pastX = mPastX[pos];
155        final float[] pastY = mPastY[pos];
156        if (drop >= 0) {
157            if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
158            final int start = drop+1;
159            final int count = NUM_PAST-drop-1;
160            System.arraycopy(pastX, start, pastX, 0, count);
161            System.arraycopy(pastY, start, pastY, 0, count);
162            System.arraycopy(pastTime, start, pastTime, 0, count);
163            i -= (drop+1);
164        }
165        pastX[i] = x;
166        pastY[i] = y;
167        pastTime[i] = time;
168        i++;
169        if (i < NUM_PAST) {
170            pastTime[i] = 0;
171        }
172    }
173
174    /**
175     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
176     * velocity of Float.MAX_VALUE.
177     *
178     * @see #computeCurrentVelocity(int, float)
179     */
180    public void computeCurrentVelocity(int units) {
181        computeCurrentVelocity(units, Float.MAX_VALUE);
182    }
183
184    /**
185     * Compute the current velocity based on the points that have been
186     * collected.  Only call this when you actually want to retrieve velocity
187     * information, as it is relatively expensive.  You can then retrieve
188     * the velocity with {@link #getXVelocity()} and
189     * {@link #getYVelocity()}.
190     *
191     * @param units The units you would like the velocity in.  A value of 1
192     * provides pixels per millisecond, 1000 provides pixels per second, etc.
193     * @param maxVelocity The maximum velocity that can be computed by this method.
194     * This value must be declared in the same unit as the units parameter. This value
195     * must be positive.
196     */
197    public void computeCurrentVelocity(int units, float maxVelocity) {
198        for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
199            final float[] pastX = mPastX[pos];
200            final float[] pastY = mPastY[pos];
201            final long[] pastTime = mPastTime[pos];
202
203            // Kind-of stupid.
204            final float oldestX = pastX[0];
205            final float oldestY = pastY[0];
206            final long oldestTime = pastTime[0];
207            float accumX = 0;
208            float accumY = 0;
209            int N=0;
210            while (N < NUM_PAST) {
211                if (pastTime[N] == 0) {
212                    break;
213                }
214                N++;
215            }
216            // Skip the last received event, since it is probably pretty noisy.
217            if (N > 3) N--;
218
219            for (int i=1; i < N; i++) {
220                final int dur = (int)(pastTime[i] - oldestTime);
221                if (dur == 0) continue;
222                float dist = pastX[i] - oldestX;
223                float vel = (dist/dur) * units;   // pixels/frame.
224                if (accumX == 0) accumX = vel;
225                else accumX = (accumX + vel) * .5f;
226
227                dist = pastY[i] - oldestY;
228                vel = (dist/dur) * units;   // pixels/frame.
229                if (accumY == 0) accumY = vel;
230                else accumY = (accumY + vel) * .5f;
231            }
232            mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
233                    : Math.min(accumX, maxVelocity);
234            mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
235                    : Math.min(accumY, maxVelocity);
236
237            if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
238                    + mXVelocity + " N=" + N);
239        }
240    }
241
242    /**
243     * Retrieve the last computed X velocity.  You must first call
244     * {@link #computeCurrentVelocity(int)} before calling this function.
245     *
246     * @return The previously computed X velocity.
247     */
248    public float getXVelocity() {
249        return mXVelocity[0];
250    }
251
252    /**
253     * Retrieve the last computed Y velocity.  You must first call
254     * {@link #computeCurrentVelocity(int)} before calling this function.
255     *
256     * @return The previously computed Y velocity.
257     */
258    public float getYVelocity() {
259        return mYVelocity[0];
260    }
261
262    /**
263     * Retrieve the last computed X velocity.  You must first call
264     * {@link #computeCurrentVelocity(int)} before calling this function.
265     *
266     * @param pos Which pointer's velocity to return.
267     * @return The previously computed X velocity.
268     *
269     * @hide Pending API approval
270     */
271    public float getXVelocity(int pos) {
272        return mXVelocity[pos];
273    }
274
275    /**
276     * Retrieve the last computed Y velocity.  You must first call
277     * {@link #computeCurrentVelocity(int)} before calling this function.
278     *
279     * @param pos Which pointer's velocity to return.
280     * @return The previously computed Y velocity.
281     *
282     * @hide Pending API approval
283     */
284    public float getYVelocity(int pos) {
285        return mYVelocity[pos];
286    }
287}
288