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