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 17 package android.view; 18 19 import android.util.Config; 20 import android.util.Log; 21 import android.util.Poolable; 22 import android.util.Pool; 23 import android.util.Pools; 24 import 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 */ 35 public 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 45 private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( 46 Pools.finitePool(new PoolableManager<VelocityTracker>() { 47 public VelocityTracker newInstance() { 48 return new VelocityTracker(); 49 } 50 51 public void onAcquired(VelocityTracker element) { 52 } 53 54 public void onReleased(VelocityTracker element) { 55 element.clear(); 56 } 57 }, 2)); 58 59 private static Pointer sRecycledPointerListHead; 60 private static int sRecycledPointerCount; 61 62 private static final class Pointer { 63 public Pointer next; 64 65 public int id; 66 public float xVelocity; 67 public float yVelocity; 68 69 public final float[] pastX = new float[NUM_PAST]; 70 public final float[] pastY = new float[NUM_PAST]; 71 public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel 72 73 public int generation; 74 } 75 76 private Pointer mPointerListHead; // sorted by id in increasing order 77 private int mLastTouchIndex; 78 private int mGeneration; 79 80 private VelocityTracker mNext; 81 82 /** 83 * Retrieve a new VelocityTracker object to watch the velocity of a 84 * motion. Be sure to call {@link #recycle} when done. You should 85 * generally only maintain an active object while tracking a movement, 86 * so that the VelocityTracker can be re-used elsewhere. 87 * 88 * @return Returns a new VelocityTracker. 89 */ 90 static public VelocityTracker obtain() { 91 return sPool.acquire(); 92 } 93 94 /** 95 * Return a VelocityTracker object back to be re-used by others. You must 96 * not touch the object after calling this function. 97 */ 98 public void recycle() { 99 sPool.release(this); 100 } 101 102 /** 103 * @hide 104 */ 105 public void setNextPoolable(VelocityTracker element) { 106 mNext = element; 107 } 108 109 /** 110 * @hide 111 */ 112 public VelocityTracker getNextPoolable() { 113 return mNext; 114 } 115 116 private VelocityTracker() { 117 clear(); 118 } 119 120 /** 121 * Reset the velocity tracker back to its initial state. 122 */ 123 public void clear() { 124 releasePointerList(mPointerListHead); 125 126 mPointerListHead = null; 127 mLastTouchIndex = 0; 128 } 129 130 /** 131 * Add a user's movement to the tracker. You should call this for the 132 * initial {@link MotionEvent#ACTION_DOWN}, the following 133 * {@link MotionEvent#ACTION_MOVE} events that you receive, and the 134 * final {@link MotionEvent#ACTION_UP}. You can, however, call this 135 * for whichever events you desire. 136 * 137 * @param ev The MotionEvent you received and would like to track. 138 */ 139 public void addMovement(MotionEvent ev) { 140 final int historySize = ev.getHistorySize(); 141 final int pointerCount = ev.getPointerCount(); 142 final int lastTouchIndex = mLastTouchIndex; 143 final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST; 144 final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST; 145 final int generation = mGeneration++; 146 147 mLastTouchIndex = finalTouchIndex; 148 149 // Update pointer data. 150 Pointer previousPointer = null; 151 for (int i = 0; i < pointerCount; i++){ 152 final int pointerId = ev.getPointerId(i); 153 154 // Find the pointer data for this pointer id. 155 // This loop is optimized for the common case where pointer ids in the event 156 // are in sorted order. However, we check for this case explicitly and 157 // perform a full linear scan from the start if needed. 158 Pointer nextPointer; 159 if (previousPointer == null || pointerId < previousPointer.id) { 160 previousPointer = null; 161 nextPointer = mPointerListHead; 162 } else { 163 nextPointer = previousPointer.next; 164 } 165 166 final Pointer pointer; 167 for (;;) { 168 if (nextPointer != null) { 169 final int nextPointerId = nextPointer.id; 170 if (nextPointerId == pointerId) { 171 pointer = nextPointer; 172 break; 173 } 174 if (nextPointerId < pointerId) { 175 nextPointer = nextPointer.next; 176 continue; 177 } 178 } 179 180 // Pointer went down. Add it to the list. 181 // Write a sentinel at the end of the pastTime trace so we will be able to 182 // tell when the trace started. 183 pointer = obtainPointer(); 184 pointer.id = pointerId; 185 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE; 186 pointer.next = nextPointer; 187 if (previousPointer == null) { 188 mPointerListHead = pointer; 189 } else { 190 previousPointer.next = pointer; 191 } 192 break; 193 } 194 195 pointer.generation = generation; 196 previousPointer = pointer; 197 198 final float[] pastX = pointer.pastX; 199 final float[] pastY = pointer.pastY; 200 final long[] pastTime = pointer.pastTime; 201 202 for (int j = 0; j < historySize; j++) { 203 final int touchIndex = (nextTouchIndex + j) % NUM_PAST; 204 pastX[touchIndex] = ev.getHistoricalX(i, j); 205 pastY[touchIndex] = ev.getHistoricalY(i, j); 206 pastTime[touchIndex] = ev.getHistoricalEventTime(j); 207 } 208 pastX[finalTouchIndex] = ev.getX(i); 209 pastY[finalTouchIndex] = ev.getY(i); 210 pastTime[finalTouchIndex] = ev.getEventTime(); 211 } 212 213 // Find removed pointers. 214 previousPointer = null; 215 for (Pointer pointer = mPointerListHead; pointer != null; ) { 216 final Pointer nextPointer = pointer.next; 217 if (pointer.generation != generation) { 218 // Pointer went up. Remove it from the list. 219 if (previousPointer == null) { 220 mPointerListHead = nextPointer; 221 } else { 222 previousPointer.next = nextPointer; 223 } 224 releasePointer(pointer); 225 } else { 226 previousPointer = pointer; 227 } 228 pointer = nextPointer; 229 } 230 } 231 232 /** 233 * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum 234 * velocity of Float.MAX_VALUE. 235 * 236 * @see #computeCurrentVelocity(int, float) 237 */ 238 public void computeCurrentVelocity(int units) { 239 computeCurrentVelocity(units, Float.MAX_VALUE); 240 } 241 242 /** 243 * Compute the current velocity based on the points that have been 244 * collected. Only call this when you actually want to retrieve velocity 245 * information, as it is relatively expensive. You can then retrieve 246 * the velocity with {@link #getXVelocity()} and 247 * {@link #getYVelocity()}. 248 * 249 * @param units The units you would like the velocity in. A value of 1 250 * provides pixels per millisecond, 1000 provides pixels per second, etc. 251 * @param maxVelocity The maximum velocity that can be computed by this method. 252 * This value must be declared in the same unit as the units parameter. This value 253 * must be positive. 254 */ 255 public void computeCurrentVelocity(int units, float maxVelocity) { 256 final int lastTouchIndex = mLastTouchIndex; 257 258 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { 259 final long[] pastTime = pointer.pastTime; 260 261 // Search backwards in time for oldest acceptable time. 262 // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE. 263 int oldestTouchIndex = lastTouchIndex; 264 int numTouches = 1; 265 final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS; 266 while (numTouches < NUM_PAST) { 267 final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST; 268 final long nextOldestTime = pastTime[nextOldestTouchIndex]; 269 if (nextOldestTime < minTime) { // also handles end of trace sentinel 270 break; 271 } 272 oldestTouchIndex = nextOldestTouchIndex; 273 numTouches += 1; 274 } 275 276 // If we have a lot of samples, skip the last received sample since it is 277 // probably pretty noisy compared to the sum of all of the traces already acquired. 278 if (numTouches > 3) { 279 numTouches -= 1; 280 } 281 282 // Kind-of stupid. 283 final float[] pastX = pointer.pastX; 284 final float[] pastY = pointer.pastY; 285 286 final float oldestX = pastX[oldestTouchIndex]; 287 final float oldestY = pastY[oldestTouchIndex]; 288 final long oldestTime = pastTime[oldestTouchIndex]; 289 290 float accumX = 0; 291 float accumY = 0; 292 293 for (int i = 1; i < numTouches; i++) { 294 final int touchIndex = (oldestTouchIndex + i) % NUM_PAST; 295 final int duration = (int)(pastTime[touchIndex] - oldestTime); 296 297 if (duration == 0) continue; 298 299 float delta = pastX[touchIndex] - oldestX; 300 float velocity = (delta / duration) * units; // pixels/frame. 301 accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f; 302 303 delta = pastY[touchIndex] - oldestY; 304 velocity = (delta / duration) * units; // pixels/frame. 305 accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f; 306 } 307 308 if (accumX < -maxVelocity) { 309 accumX = - maxVelocity; 310 } else if (accumX > maxVelocity) { 311 accumX = maxVelocity; 312 } 313 314 if (accumY < -maxVelocity) { 315 accumY = - maxVelocity; 316 } else if (accumY > maxVelocity) { 317 accumY = maxVelocity; 318 } 319 320 pointer.xVelocity = accumX; 321 pointer.yVelocity = accumY; 322 323 if (localLOGV) { 324 Log.v(TAG, "Pointer " + pointer.id 325 + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches); 326 } 327 } 328 } 329 330 /** 331 * Retrieve the last computed X velocity. You must first call 332 * {@link #computeCurrentVelocity(int)} before calling this function. 333 * 334 * @return The previously computed X velocity. 335 */ 336 public float getXVelocity() { 337 Pointer pointer = getPointer(0); 338 return pointer != null ? pointer.xVelocity : 0; 339 } 340 341 /** 342 * Retrieve the last computed Y velocity. You must first call 343 * {@link #computeCurrentVelocity(int)} before calling this function. 344 * 345 * @return The previously computed Y velocity. 346 */ 347 public float getYVelocity() { 348 Pointer pointer = getPointer(0); 349 return pointer != null ? pointer.yVelocity : 0; 350 } 351 352 /** 353 * Retrieve the last computed X velocity. You must first call 354 * {@link #computeCurrentVelocity(int)} before calling this function. 355 * 356 * @param id Which pointer's velocity to return. 357 * @return The previously computed X velocity. 358 */ 359 public float getXVelocity(int id) { 360 Pointer pointer = getPointer(id); 361 return pointer != null ? pointer.xVelocity : 0; 362 } 363 364 /** 365 * Retrieve the last computed Y velocity. You must first call 366 * {@link #computeCurrentVelocity(int)} before calling this function. 367 * 368 * @param id Which pointer's velocity to return. 369 * @return The previously computed Y velocity. 370 */ 371 public float getYVelocity(int id) { 372 Pointer pointer = getPointer(id); 373 return pointer != null ? pointer.yVelocity : 0; 374 } 375 376 private final Pointer getPointer(int id) { 377 for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { 378 if (pointer.id == id) { 379 return pointer; 380 } 381 } 382 return null; 383 } 384 385 private static final Pointer obtainPointer() { 386 synchronized (sPool) { 387 if (sRecycledPointerCount != 0) { 388 Pointer element = sRecycledPointerListHead; 389 sRecycledPointerCount -= 1; 390 sRecycledPointerListHead = element.next; 391 element.next = null; 392 return element; 393 } 394 } 395 return new Pointer(); 396 } 397 398 private static final void releasePointer(Pointer pointer) { 399 synchronized (sPool) { 400 if (sRecycledPointerCount < POINTER_POOL_CAPACITY) { 401 pointer.next = sRecycledPointerListHead; 402 sRecycledPointerCount += 1; 403 sRecycledPointerListHead = pointer; 404 } 405 } 406 } 407 408 private static final void releasePointerList(Pointer pointer) { 409 if (pointer != null) { 410 synchronized (sPool) { 411 int count = sRecycledPointerCount; 412 if (count >= POINTER_POOL_CAPACITY) { 413 return; 414 } 415 416 Pointer tail = pointer; 417 for (;;) { 418 count += 1; 419 if (count >= POINTER_POOL_CAPACITY) { 420 break; 421 } 422 423 Pointer next = tail.next; 424 if (next == null) { 425 break; 426 } 427 tail = next; 428 } 429 430 tail.next = sRecycledPointerListHead; 431 sRecycledPointerCount = count; 432 sRecycledPointerListHead = pointer; 433 } 434 } 435 } 436 } 437