VelocityTracker.java revision 8ae0f3f6c7319717237af74c8d4aad2c082eef2b
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 private static final String TAG = "VelocityTracker"; 37 private static final boolean DEBUG = false; 38 private static final boolean localLOGV = DEBUG || Config.LOGV; 39 40 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; 44 private static final int INVALID_POINTER = -1; 45 46 private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( 47 Pools.finitePool(new PoolableManager<VelocityTracker>() { 48 public VelocityTracker newInstance() { 49 return new VelocityTracker(); 50 } 51 52 public void onAcquired(VelocityTracker element) { 53 } 54 55 public void onReleased(VelocityTracker element) { 56 element.clear(); 57 } 58 }, 2)); 59 60 private static Pointer sRecycledPointerListHead; 61 private static int sRecycledPointerCount; 62 63 private static final class Pointer { 64 public Pointer next; 65 66 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 73 74 public int generation; 75 } 76 77 private Pointer mPointerListHead; // sorted by id in increasing order 78 private int mLastTouchIndex; 79 private int mGeneration; 80 private int mActivePointerId; 81 82 private VelocityTracker mNext; 83 84 /** 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. 89 * 90 * @return Returns a new VelocityTracker. 91 */ 92 static public VelocityTracker obtain() { 93 return sPool.acquire(); 94 } 95 96 /** 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() { 101 sPool.release(this); 102 } 103 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 118 private VelocityTracker() { 119 clear(); 120 } 121 122 /** 123 * Reset the velocity tracker back to its initial state. 124 */ 125 public void clear() { 126 releasePointerList(mPointerListHead); 127 128 mPointerListHead = null; 129 mLastTouchIndex = 0; 130 mActivePointerId = INVALID_POINTER; 131 } 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) { 143 final int historySize = ev.getHistorySize(); 144 final int pointerCount = ev.getPointerCount(); 145 final int lastTouchIndex = mLastTouchIndex; 146 final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST; 147 final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST; 148 final int generation = mGeneration++; 149 150 mLastTouchIndex = finalTouchIndex; 151 152 // Update pointer data. 153 Pointer previousPointer = null; 154 for (int i = 0; i < pointerCount; i++){ 155 final int pointerId = ev.getPointerId(i); 156 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; 167 } 168 169 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. 186 if (mActivePointerId == INVALID_POINTER) { 187 // Congratulations! You're the new active pointer! 188 mActivePointerId = pointerId; 189 } 190 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; 208 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(); 218 } 219 220 // Find removed pointers. 221 previousPointer = null; 222 for (Pointer pointer = mPointerListHead; pointer != null; ) { 223 final Pointer nextPointer = pointer.next; 224 final int pointerId = pointer.id; 225 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); 233 234 if (pointerId == mActivePointerId) { 235 // Pick a new active pointer. How is arbitrary. 236 mActivePointerId = mPointerListHead != null ? 237 mPointerListHead.id : INVALID_POINTER; 238 } 239 } else { 240 previousPointer = pointer; 241 } 242 pointer = nextPointer; 243 } 244 } 245 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 256 /** 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. 265 * @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. 268 */ 269 public void computeCurrentVelocity(int units, float maxVelocity) { 270 final int lastTouchIndex = mLastTouchIndex; 271 272 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { 273 final long[] pastTime = pointer.pastTime; 274 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; 285 } 286 oldestTouchIndex = nextOldestTouchIndex; 287 numTouches += 1; 288 } 289 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 296 // Kind-of stupid. 297 final float[] pastX = pointer.pastX; 298 final float[] pastY = pointer.pastY; 299 300 final float oldestX = pastX[oldestTouchIndex]; 301 final float oldestY = pastY[oldestTouchIndex]; 302 final long oldestTime = pastTime[oldestTouchIndex]; 303 304 float accumX = 0; 305 float accumY = 0; 306 307 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; 320 } 321 322 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 334 pointer.xVelocity = accumX; 335 pointer.yVelocity = accumY; 336 337 if (localLOGV) { 338 Log.v(TAG, "Pointer " + pointer.id 339 + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches); 340 } 341 } 342 } 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() { 351 Pointer pointer = getPointer(mActivePointerId); 352 return pointer != null ? pointer.xVelocity : 0; 353 } 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() { 362 Pointer pointer = getPointer(mActivePointerId); 363 return pointer != null ? pointer.yVelocity : 0; 364 } 365 366 /** 367 * Retrieve the last computed X velocity. You must first call 368 * {@link #computeCurrentVelocity(int)} before calling this function. 369 * 370 * @param id Which pointer's velocity to return. 371 * @return The previously computed X velocity. 372 */ 373 public float getXVelocity(int id) { 374 Pointer pointer = getPointer(id); 375 return pointer != null ? pointer.xVelocity : 0; 376 } 377 378 /** 379 * Retrieve the last computed Y velocity. You must first call 380 * {@link #computeCurrentVelocity(int)} before calling this function. 381 * 382 * @param id Which pointer's velocity to return. 383 * @return The previously computed Y velocity. 384 */ 385 public float getYVelocity(int id) { 386 Pointer pointer = getPointer(id); 387 return pointer != null ? pointer.yVelocity : 0; 388 } 389 390 private final Pointer getPointer(int id) { 391 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { 392 if (pointer.id == id) { 393 return pointer; 394 } 395 } 396 return null; 397 } 398 399 private static final Pointer obtainPointer() { 400 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 412 private static final void releasePointer(Pointer pointer) { 413 synchronized (sPool) { 414 if (sRecycledPointerCount < POINTER_POOL_CAPACITY) { 415 pointer.next = sRecycledPointerListHead; 416 sRecycledPointerCount += 1; 417 sRecycledPointerListHead = pointer; 418 } 419 } 420 } 421 422 private static final void releasePointerList(Pointer pointer) { 423 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 } 450} 451