blob: 4ab2881f79a7a3c32522a9088319c48f260b096e [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Romain Guyd928d682009-03-31 17:52:16 -070021import android.util.Poolable;
22import android.util.Pool;
Romain Guy2e9bbce2009-04-01 10:40:10 -070023import android.util.Pools;
Romain Guyd928d682009-03-31 17:52:16 -070024import android.util.PoolableManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025
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 */
Romain Guyd928d682009-03-31 17:52:16 -070035public final class VelocityTracker implements Poolable<VelocityTracker> {
Jeff Brown88cf2fc2010-08-09 18:50:35 -070036 private static final String TAG = "VelocityTracker";
37 private static final boolean DEBUG = false;
38 private static final boolean localLOGV = DEBUG || Config.LOGV;
Romain Guyd928d682009-03-31 17:52:16 -070039
Jeff Brown88cf2fc2010-08-09 18:50:35 -070040 private static final int NUM_PAST = 10;
41 private static final int MAX_AGE_MILLISECONDS = 200;
42
43 private static final int POINTER_POOL_CAPACITY = 20;
Adam Powell8ae0f3f2010-09-09 11:54:19 -070044 private static final int INVALID_POINTER = -1;
Romain Guyd928d682009-03-31 17:52:16 -070045
Romain Guy2e9bbce2009-04-01 10:40:10 -070046 private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
47 Pools.finitePool(new PoolableManager<VelocityTracker>() {
Romain Guyd928d682009-03-31 17:52:16 -070048 public VelocityTracker newInstance() {
49 return new VelocityTracker();
50 }
51
52 public void onAcquired(VelocityTracker element) {
Romain Guyd928d682009-03-31 17:52:16 -070053 }
54
55 public void onReleased(VelocityTracker element) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -070056 element.clear();
Romain Guyd928d682009-03-31 17:52:16 -070057 }
58 }, 2));
Jeff Brown9e2ad362010-07-30 19:20:11 -070059
Jeff Brown88cf2fc2010-08-09 18:50:35 -070060 private static Pointer sRecycledPointerListHead;
61 private static int sRecycledPointerCount;
Jeff Brown9e2ad362010-07-30 19:20:11 -070062
Jeff Brown88cf2fc2010-08-09 18:50:35 -070063 private static final class Pointer {
64 public Pointer next;
65
Jeff Brown9e2ad362010-07-30 19:20:11 -070066 public int id;
67 public float xVelocity;
68 public float yVelocity;
69
70 public final float[] pastX = new float[NUM_PAST];
71 public final float[] pastY = new float[NUM_PAST];
72 public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
Jeff Brown88cf2fc2010-08-09 18:50:35 -070073
74 public int generation;
Jeff Brown9e2ad362010-07-30 19:20:11 -070075 }
76
Jeff Brown88cf2fc2010-08-09 18:50:35 -070077 private Pointer mPointerListHead; // sorted by id in increasing order
Jeff Brown9e2ad362010-07-30 19:20:11 -070078 private int mLastTouchIndex;
Jeff Brown88cf2fc2010-08-09 18:50:35 -070079 private int mGeneration;
Adam Powell8ae0f3f2010-09-09 11:54:19 -070080 private int mActivePointerId;
Romain Guyd928d682009-03-31 17:52:16 -070081
82 private VelocityTracker mNext;
83
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 /**
85 * Retrieve a new VelocityTracker object to watch the velocity of a
86 * motion. Be sure to call {@link #recycle} when done. You should
87 * generally only maintain an active object while tracking a movement,
88 * so that the VelocityTracker can be re-used elsewhere.
Romain Guyd928d682009-03-31 17:52:16 -070089 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 * @return Returns a new VelocityTracker.
91 */
92 static public VelocityTracker obtain() {
Romain Guyd928d682009-03-31 17:52:16 -070093 return sPool.acquire();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 }
Romain Guyd928d682009-03-31 17:52:16 -070095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 /**
97 * Return a VelocityTracker object back to be re-used by others. You must
98 * not touch the object after calling this function.
99 */
100 public void recycle() {
Romain Guyd928d682009-03-31 17:52:16 -0700101 sPool.release(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 }
Romain Guyd928d682009-03-31 17:52:16 -0700103
104 /**
105 * @hide
106 */
107 public void setNextPoolable(VelocityTracker element) {
108 mNext = element;
109 }
110
111 /**
112 * @hide
113 */
114 public VelocityTracker getNextPoolable() {
115 return mNext;
116 }
117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 private VelocityTracker() {
Adam Powell0bba68d2010-03-03 19:50:49 -0800119 clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 }
121
122 /**
123 * Reset the velocity tracker back to its initial state.
124 */
125 public void clear() {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700126 releasePointerList(mPointerListHead);
127
128 mPointerListHead = null;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700129 mLastTouchIndex = 0;
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700130 mActivePointerId = INVALID_POINTER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 }
132
133 /**
134 * Add a user's movement to the tracker. You should call this for the
135 * initial {@link MotionEvent#ACTION_DOWN}, the following
136 * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
137 * final {@link MotionEvent#ACTION_UP}. You can, however, call this
138 * for whichever events you desire.
139 *
140 * @param ev The MotionEvent you received and would like to track.
141 */
142 public void addMovement(MotionEvent ev) {
Jeff Brown9e2ad362010-07-30 19:20:11 -0700143 final int historySize = ev.getHistorySize();
Adam Powell8acdb202010-01-06 17:33:52 -0800144 final int pointerCount = ev.getPointerCount();
Jeff Brown9e2ad362010-07-30 19:20:11 -0700145 final int lastTouchIndex = mLastTouchIndex;
146 final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
147 final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700148 final int generation = mGeneration++;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700149
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700150 mLastTouchIndex = finalTouchIndex;
151
152 // Update pointer data.
153 Pointer previousPointer = null;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700154 for (int i = 0; i < pointerCount; i++){
155 final int pointerId = ev.getPointerId(i);
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700156
157 // Find the pointer data for this pointer id.
158 // This loop is optimized for the common case where pointer ids in the event
159 // are in sorted order. However, we check for this case explicitly and
160 // perform a full linear scan from the start if needed.
161 Pointer nextPointer;
162 if (previousPointer == null || pointerId < previousPointer.id) {
163 previousPointer = null;
164 nextPointer = mPointerListHead;
165 } else {
166 nextPointer = previousPointer.next;
Adam Powell73d8fca2010-02-12 16:50:19 -0800167 }
Jeff Brown9e2ad362010-07-30 19:20:11 -0700168
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700169 final Pointer pointer;
170 for (;;) {
171 if (nextPointer != null) {
172 final int nextPointerId = nextPointer.id;
173 if (nextPointerId == pointerId) {
174 pointer = nextPointer;
175 break;
176 }
177 if (nextPointerId < pointerId) {
178 nextPointer = nextPointer.next;
179 continue;
180 }
181 }
182
183 // Pointer went down. Add it to the list.
184 // Write a sentinel at the end of the pastTime trace so we will be able to
185 // tell when the trace started.
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700186 if (mActivePointerId == INVALID_POINTER) {
187 // Congratulations! You're the new active pointer!
188 mActivePointerId = pointerId;
189 }
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700190 pointer = obtainPointer();
191 pointer.id = pointerId;
192 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
193 pointer.next = nextPointer;
194 if (previousPointer == null) {
195 mPointerListHead = pointer;
196 } else {
197 previousPointer.next = pointer;
198 }
199 break;
200 }
201
202 pointer.generation = generation;
203 previousPointer = pointer;
204
205 final float[] pastX = pointer.pastX;
206 final float[] pastY = pointer.pastY;
207 final long[] pastTime = pointer.pastTime;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700208
209 for (int j = 0; j < historySize; j++) {
210 final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
211 pastX[touchIndex] = ev.getHistoricalX(i, j);
212 pastY[touchIndex] = ev.getHistoricalY(i, j);
213 pastTime[touchIndex] = ev.getHistoricalEventTime(j);
214 }
215 pastX[finalTouchIndex] = ev.getX(i);
216 pastY[finalTouchIndex] = ev.getY(i);
217 pastTime[finalTouchIndex] = ev.getEventTime();
Adam Powell73d8fca2010-02-12 16:50:19 -0800218 }
Jeff Brown9e2ad362010-07-30 19:20:11 -0700219
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700220 // Find removed pointers.
221 previousPointer = null;
222 for (Pointer pointer = mPointerListHead; pointer != null; ) {
223 final Pointer nextPointer = pointer.next;
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700224 final int pointerId = pointer.id;
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700225 if (pointer.generation != generation) {
226 // Pointer went up. Remove it from the list.
227 if (previousPointer == null) {
228 mPointerListHead = nextPointer;
229 } else {
230 previousPointer.next = nextPointer;
231 }
232 releasePointer(pointer);
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700233
234 if (pointerId == mActivePointerId) {
235 // Pick a new active pointer. How is arbitrary.
236 mActivePointerId = mPointerListHead != null ?
237 mPointerListHead.id : INVALID_POINTER;
238 }
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700239 } else {
240 previousPointer = pointer;
241 }
242 pointer = nextPointer;
243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 }
Romain Guy4296fc42009-07-06 11:48:52 -0700245
246 /**
247 * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
248 * velocity of Float.MAX_VALUE.
249 *
250 * @see #computeCurrentVelocity(int, float)
251 */
252 public void computeCurrentVelocity(int units) {
253 computeCurrentVelocity(units, Float.MAX_VALUE);
254 }
255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 /**
257 * Compute the current velocity based on the points that have been
258 * collected. Only call this when you actually want to retrieve velocity
259 * information, as it is relatively expensive. You can then retrieve
260 * the velocity with {@link #getXVelocity()} and
261 * {@link #getYVelocity()}.
262 *
263 * @param units The units you would like the velocity in. A value of 1
264 * provides pixels per millisecond, 1000 provides pixels per second, etc.
Romain Guy4296fc42009-07-06 11:48:52 -0700265 * @param maxVelocity The maximum velocity that can be computed by this method.
266 * This value must be declared in the same unit as the units parameter. This value
267 * must be positive.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 */
Romain Guy4296fc42009-07-06 11:48:52 -0700269 public void computeCurrentVelocity(int units, float maxVelocity) {
Jeff Brown9e2ad362010-07-30 19:20:11 -0700270 final int lastTouchIndex = mLastTouchIndex;
Marc Capdeviellece760cd2010-02-03 11:15:38 +0100271
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700272 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
273 final long[] pastTime = pointer.pastTime;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700274
275 // Search backwards in time for oldest acceptable time.
276 // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
277 int oldestTouchIndex = lastTouchIndex;
278 int numTouches = 1;
279 final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
280 while (numTouches < NUM_PAST) {
281 final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
282 final long nextOldestTime = pastTime[nextOldestTouchIndex];
283 if (nextOldestTime < minTime) { // also handles end of trace sentinel
284 break;
Adam Powell87da8602010-02-09 13:19:25 -0800285 }
Jeff Brown9e2ad362010-07-30 19:20:11 -0700286 oldestTouchIndex = nextOldestTouchIndex;
287 numTouches += 1;
Marc Capdeviellece760cd2010-02-03 11:15:38 +0100288 }
Jeff Brown9e2ad362010-07-30 19:20:11 -0700289
290 // If we have a lot of samples, skip the last received sample since it is
291 // probably pretty noisy compared to the sum of all of the traces already acquired.
292 if (numTouches > 3) {
293 numTouches -= 1;
294 }
295
Adam Powell8acdb202010-01-06 17:33:52 -0800296 // Kind-of stupid.
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700297 final float[] pastX = pointer.pastX;
298 final float[] pastY = pointer.pastY;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700299
300 final float oldestX = pastX[oldestTouchIndex];
301 final float oldestY = pastY[oldestTouchIndex];
302 final long oldestTime = pastTime[oldestTouchIndex];
303
Adam Powell8acdb202010-01-06 17:33:52 -0800304 float accumX = 0;
305 float accumY = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306
Jeff Brown9e2ad362010-07-30 19:20:11 -0700307 for (int i = 1; i < numTouches; i++) {
308 final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
309 final int duration = (int)(pastTime[touchIndex] - oldestTime);
310
311 if (duration == 0) continue;
312
313 float delta = pastX[touchIndex] - oldestX;
314 float velocity = (delta / duration) * units; // pixels/frame.
315 accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
316
317 delta = pastY[touchIndex] - oldestY;
318 velocity = (delta / duration) * units; // pixels/frame.
319 accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
Adam Powell8acdb202010-01-06 17:33:52 -0800320 }
Adam Powell87da8602010-02-09 13:19:25 -0800321
Jeff Brown9e2ad362010-07-30 19:20:11 -0700322 if (accumX < -maxVelocity) {
323 accumX = - maxVelocity;
324 } else if (accumX > maxVelocity) {
325 accumX = maxVelocity;
326 }
327
328 if (accumY < -maxVelocity) {
329 accumY = - maxVelocity;
330 } else if (accumY > maxVelocity) {
331 accumY = maxVelocity;
332 }
333
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700334 pointer.xVelocity = accumX;
335 pointer.yVelocity = accumY;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700336
337 if (localLOGV) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700338 Log.v(TAG, "Pointer " + pointer.id
Jeff Brown9e2ad362010-07-30 19:20:11 -0700339 + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
340 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 }
343
344 /**
345 * Retrieve the last computed X velocity. You must first call
346 * {@link #computeCurrentVelocity(int)} before calling this function.
347 *
348 * @return The previously computed X velocity.
349 */
350 public float getXVelocity() {
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700351 Pointer pointer = getPointer(mActivePointerId);
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700352 return pointer != null ? pointer.xVelocity : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 }
354
355 /**
356 * Retrieve the last computed Y velocity. You must first call
357 * {@link #computeCurrentVelocity(int)} before calling this function.
358 *
359 * @return The previously computed Y velocity.
360 */
361 public float getYVelocity() {
Adam Powell8ae0f3f2010-09-09 11:54:19 -0700362 Pointer pointer = getPointer(mActivePointerId);
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700363 return pointer != null ? pointer.yVelocity : 0;
Adam Powell8acdb202010-01-06 17:33:52 -0800364 }
365
366 /**
367 * Retrieve the last computed X velocity. You must first call
368 * {@link #computeCurrentVelocity(int)} before calling this function.
369 *
Adam Powell73d8fca2010-02-12 16:50:19 -0800370 * @param id Which pointer's velocity to return.
Adam Powell8acdb202010-01-06 17:33:52 -0800371 * @return The previously computed X velocity.
Adam Powell8acdb202010-01-06 17:33:52 -0800372 */
Adam Powell73d8fca2010-02-12 16:50:19 -0800373 public float getXVelocity(int id) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700374 Pointer pointer = getPointer(id);
375 return pointer != null ? pointer.xVelocity : 0;
Adam Powell8acdb202010-01-06 17:33:52 -0800376 }
377
378 /**
379 * Retrieve the last computed Y velocity. You must first call
380 * {@link #computeCurrentVelocity(int)} before calling this function.
381 *
Adam Powell73d8fca2010-02-12 16:50:19 -0800382 * @param id Which pointer's velocity to return.
Adam Powell8acdb202010-01-06 17:33:52 -0800383 * @return The previously computed Y velocity.
Adam Powell8acdb202010-01-06 17:33:52 -0800384 */
Adam Powell73d8fca2010-02-12 16:50:19 -0800385 public float getYVelocity(int id) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700386 Pointer pointer = getPointer(id);
387 return pointer != null ? pointer.yVelocity : 0;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700388 }
389
Romain Guyc00972b2010-10-12 11:31:07 -0700390 private Pointer getPointer(int id) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700391 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
392 if (pointer.id == id) {
393 return pointer;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700394 }
395 }
396 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 }
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700398
Romain Guyc00972b2010-10-12 11:31:07 -0700399 private static Pointer obtainPointer() {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700400 synchronized (sPool) {
401 if (sRecycledPointerCount != 0) {
402 Pointer element = sRecycledPointerListHead;
403 sRecycledPointerCount -= 1;
404 sRecycledPointerListHead = element.next;
405 element.next = null;
406 return element;
407 }
408 }
409 return new Pointer();
410 }
411
Romain Guyc00972b2010-10-12 11:31:07 -0700412 private static void releasePointer(Pointer pointer) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700413 synchronized (sPool) {
414 if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
415 pointer.next = sRecycledPointerListHead;
416 sRecycledPointerCount += 1;
417 sRecycledPointerListHead = pointer;
418 }
419 }
420 }
421
Romain Guyc00972b2010-10-12 11:31:07 -0700422 private static void releasePointerList(Pointer pointer) {
Jeff Brown88cf2fc2010-08-09 18:50:35 -0700423 if (pointer != null) {
424 synchronized (sPool) {
425 int count = sRecycledPointerCount;
426 if (count >= POINTER_POOL_CAPACITY) {
427 return;
428 }
429
430 Pointer tail = pointer;
431 for (;;) {
432 count += 1;
433 if (count >= POINTER_POOL_CAPACITY) {
434 break;
435 }
436
437 Pointer next = tail.next;
438 if (next == null) {
439 break;
440 }
441 tail = next;
442 }
443
444 tail.next = sRecycledPointerListHead;
445 sRecycledPointerCount = count;
446 sRecycledPointerListHead = pointer;
447 }
448 }
449 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450}