VelocityTracker.java revision 9e2ad36be87f2703b3d737189944d82f93bd4f27
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 MAX_AGE_MILLISECONDS = 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    private static final int INITIAL_POINTERS = 5;
59
60    private static final class PointerData {
61        public int id;
62        public float xVelocity;
63        public float yVelocity;
64
65        public final float[] pastX = new float[NUM_PAST];
66        public final float[] pastY = new float[NUM_PAST];
67        public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
68    }
69
70    private PointerData[] mPointers = new PointerData[INITIAL_POINTERS];
71    private int mNumPointers;
72    private int mLastTouchIndex;
73
74    private VelocityTracker mNext;
75
76    /**
77     * Retrieve a new VelocityTracker object to watch the velocity of a
78     * motion.  Be sure to call {@link #recycle} when done.  You should
79     * generally only maintain an active object while tracking a movement,
80     * so that the VelocityTracker can be re-used elsewhere.
81     *
82     * @return Returns a new VelocityTracker.
83     */
84    static public VelocityTracker obtain() {
85        return sPool.acquire();
86    }
87
88    /**
89     * Return a VelocityTracker object back to be re-used by others.  You must
90     * not touch the object after calling this function.
91     */
92    public void recycle() {
93        sPool.release(this);
94    }
95
96    /**
97     * @hide
98     */
99    public void setNextPoolable(VelocityTracker element) {
100        mNext = element;
101    }
102
103    /**
104     * @hide
105     */
106    public VelocityTracker getNextPoolable() {
107        return mNext;
108    }
109
110    private VelocityTracker() {
111        clear();
112    }
113
114    /**
115     * Reset the velocity tracker back to its initial state.
116     */
117    public void clear() {
118        mNumPointers = 0;
119        mLastTouchIndex = 0;
120    }
121
122    /**
123     * Add a user's movement to the tracker.  You should call this for the
124     * initial {@link MotionEvent#ACTION_DOWN}, the following
125     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
126     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
127     * for whichever events you desire.
128     *
129     * @param ev The MotionEvent you received and would like to track.
130     */
131    public void addMovement(MotionEvent ev) {
132        final int historySize = ev.getHistorySize();
133        final int pointerCount = ev.getPointerCount();
134        final int lastTouchIndex = mLastTouchIndex;
135        final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
136        final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
137
138        if (pointerCount < mNumPointers) {
139            final PointerData[] pointers = mPointers;
140            int i = mNumPointers;
141            while (--i >= 0) {
142                final PointerData pointerData = pointers[i];
143                if (ev.findPointerIndex(pointerData.id) == -1) {
144                    // Pointer went up.
145                    // Shuffle pointers down to fill the hole.  Place the old pointer data at
146                    // the end so we can recycle it if more pointers are added later.
147                    mNumPointers -= 1;
148                    final int remaining = mNumPointers - i;
149                    if (remaining != 0) {
150                        System.arraycopy(pointers, i + 1, pointers, i, remaining);
151                        pointers[mNumPointers] = pointerData;
152                    }
153                }
154            }
155        }
156
157        for (int i = 0; i < pointerCount; i++){
158            final int pointerId = ev.getPointerId(i);
159            PointerData pointerData = getPointerData(pointerId);
160            if (pointerData == null) {
161                // Pointer went down.
162                // Add a new entry.  Write a sentinel at the end of the pastTime trace so we
163                // will be able to tell where the trace started.
164                final PointerData[] oldPointers = mPointers;
165                final int newPointerIndex = mNumPointers;
166                if (newPointerIndex < oldPointers.length) {
167                    pointerData = oldPointers[newPointerIndex];
168                    if (pointerData == null) {
169                        pointerData = new PointerData();
170                        oldPointers[newPointerIndex] = pointerData;
171                    }
172                } else {
173                    final PointerData[] newPointers = new PointerData[newPointerIndex * 2];
174                    System.arraycopy(oldPointers, 0, newPointers, 0, newPointerIndex);
175                    mPointers = newPointers;
176                    pointerData = new PointerData();
177                    newPointers[newPointerIndex] = pointerData;
178                }
179                pointerData.id = pointerId;
180                pointerData.pastTime[lastTouchIndex] = Long.MIN_VALUE;
181                mNumPointers += 1;
182            }
183
184            final float[] pastX = pointerData.pastX;
185            final float[] pastY = pointerData.pastY;
186            final long[] pastTime = pointerData.pastTime;
187
188            for (int j = 0; j < historySize; j++) {
189                final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
190                pastX[touchIndex] = ev.getHistoricalX(i, j);
191                pastY[touchIndex] = ev.getHistoricalY(i, j);
192                pastTime[touchIndex] = ev.getHistoricalEventTime(j);
193            }
194            pastX[finalTouchIndex] = ev.getX(i);
195            pastY[finalTouchIndex] = ev.getY(i);
196            pastTime[finalTouchIndex] = ev.getEventTime();
197        }
198
199        mLastTouchIndex = finalTouchIndex;
200    }
201
202    /**
203     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
204     * velocity of Float.MAX_VALUE.
205     *
206     * @see #computeCurrentVelocity(int, float)
207     */
208    public void computeCurrentVelocity(int units) {
209        computeCurrentVelocity(units, Float.MAX_VALUE);
210    }
211
212    /**
213     * Compute the current velocity based on the points that have been
214     * collected.  Only call this when you actually want to retrieve velocity
215     * information, as it is relatively expensive.  You can then retrieve
216     * the velocity with {@link #getXVelocity()} and
217     * {@link #getYVelocity()}.
218     *
219     * @param units The units you would like the velocity in.  A value of 1
220     * provides pixels per millisecond, 1000 provides pixels per second, etc.
221     * @param maxVelocity The maximum velocity that can be computed by this method.
222     * This value must be declared in the same unit as the units parameter. This value
223     * must be positive.
224     */
225    public void computeCurrentVelocity(int units, float maxVelocity) {
226        final int numPointers = mNumPointers;
227        final PointerData[] pointers = mPointers;
228        final int lastTouchIndex = mLastTouchIndex;
229
230        for (int p = 0; p < numPointers; p++) {
231            final PointerData pointerData = pointers[p];
232            final long[] pastTime = pointerData.pastTime;
233
234            // Search backwards in time for oldest acceptable time.
235            // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
236            int oldestTouchIndex = lastTouchIndex;
237            int numTouches = 1;
238            final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
239            while (numTouches < NUM_PAST) {
240                final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
241                final long nextOldestTime = pastTime[nextOldestTouchIndex];
242                if (nextOldestTime < minTime) { // also handles end of trace sentinel
243                    break;
244                }
245                oldestTouchIndex = nextOldestTouchIndex;
246                numTouches += 1;
247            }
248
249            // If we have a lot of samples, skip the last received sample since it is
250            // probably pretty noisy compared to the sum of all of the traces already acquired.
251            if (numTouches > 3) {
252                numTouches -= 1;
253            }
254
255            // Kind-of stupid.
256            final float[] pastX = pointerData.pastX;
257            final float[] pastY = pointerData.pastY;
258
259            final float oldestX = pastX[oldestTouchIndex];
260            final float oldestY = pastY[oldestTouchIndex];
261            final long oldestTime = pastTime[oldestTouchIndex];
262
263            float accumX = 0;
264            float accumY = 0;
265
266            for (int i = 1; i < numTouches; i++) {
267                final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
268                final int duration = (int)(pastTime[touchIndex] - oldestTime);
269
270                if (duration == 0) continue;
271
272                float delta = pastX[touchIndex] - oldestX;
273                float velocity = (delta / duration) * units; // pixels/frame.
274                accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
275
276                delta = pastY[touchIndex] - oldestY;
277                velocity = (delta / duration) * units; // pixels/frame.
278                accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
279            }
280
281            if (accumX < -maxVelocity) {
282                accumX = - maxVelocity;
283            } else if (accumX > maxVelocity) {
284                accumX = maxVelocity;
285            }
286
287            if (accumY < -maxVelocity) {
288                accumY = - maxVelocity;
289            } else if (accumY > maxVelocity) {
290                accumY = maxVelocity;
291            }
292
293            pointerData.xVelocity = accumX;
294            pointerData.yVelocity = accumY;
295
296            if (localLOGV) {
297                Log.v(TAG, "[" + p + "] Pointer " + pointerData.id
298                    + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
299            }
300        }
301    }
302
303    /**
304     * Retrieve the last computed X velocity.  You must first call
305     * {@link #computeCurrentVelocity(int)} before calling this function.
306     *
307     * @return The previously computed X velocity.
308     */
309    public float getXVelocity() {
310        PointerData pointerData = getPointerData(0);
311        return pointerData != null ? pointerData.xVelocity : 0;
312    }
313
314    /**
315     * Retrieve the last computed Y velocity.  You must first call
316     * {@link #computeCurrentVelocity(int)} before calling this function.
317     *
318     * @return The previously computed Y velocity.
319     */
320    public float getYVelocity() {
321        PointerData pointerData = getPointerData(0);
322        return pointerData != null ? pointerData.yVelocity : 0;
323    }
324
325    /**
326     * Retrieve the last computed X velocity.  You must first call
327     * {@link #computeCurrentVelocity(int)} before calling this function.
328     *
329     * @param id Which pointer's velocity to return.
330     * @return The previously computed X velocity.
331     */
332    public float getXVelocity(int id) {
333        PointerData pointerData = getPointerData(id);
334        return pointerData != null ? pointerData.xVelocity : 0;
335    }
336
337    /**
338     * Retrieve the last computed Y velocity.  You must first call
339     * {@link #computeCurrentVelocity(int)} before calling this function.
340     *
341     * @param id Which pointer's velocity to return.
342     * @return The previously computed Y velocity.
343     */
344    public float getYVelocity(int id) {
345        PointerData pointerData = getPointerData(id);
346        return pointerData != null ? pointerData.yVelocity : 0;
347    }
348
349    private final PointerData getPointerData(int id) {
350        final PointerData[] pointers = mPointers;
351        final int numPointers = mNumPointers;
352        for (int p = 0; p < numPointers; p++) {
353            PointerData pointerData = pointers[p];
354            if (pointerData.id == id) {
355                return pointerData;
356            }
357        }
358        return null;
359    }
360}
361