VelocityTracker.java revision b1110149cccc3b99e59ead34ca46e5ac026f6db9
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 LONGEST_PAST_TIME = 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 final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; 59 final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; 60 final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; 61 62 float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; 63 float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; 64 int mLastTouch; 65 66 private VelocityTracker mNext; 67 68 /** 69 * Retrieve a new VelocityTracker object to watch the velocity of a 70 * motion. Be sure to call {@link #recycle} when done. You should 71 * generally only maintain an active object while tracking a movement, 72 * so that the VelocityTracker can be re-used elsewhere. 73 * 74 * @return Returns a new VelocityTracker. 75 */ 76 static public VelocityTracker obtain() { 77 return sPool.acquire(); 78 } 79 80 /** 81 * Return a VelocityTracker object back to be re-used by others. You must 82 * not touch the object after calling this function. 83 */ 84 public void recycle() { 85 sPool.release(this); 86 } 87 88 /** 89 * @hide 90 */ 91 public void setNextPoolable(VelocityTracker element) { 92 mNext = element; 93 } 94 95 /** 96 * @hide 97 */ 98 public VelocityTracker getNextPoolable() { 99 return mNext; 100 } 101 102 private VelocityTracker() { 103 clear(); 104 } 105 106 /** 107 * Reset the velocity tracker back to its initial state. 108 */ 109 public void clear() { 110 final long[][] pastTime = mPastTime; 111 for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) { 112 for (int i = 0; i < NUM_PAST; i++) { 113 pastTime[p][i] = Long.MIN_VALUE; 114 } 115 } 116 } 117 118 /** 119 * Add a user's movement to the tracker. You should call this for the 120 * initial {@link MotionEvent#ACTION_DOWN}, the following 121 * {@link MotionEvent#ACTION_MOVE} events that you receive, and the 122 * final {@link MotionEvent#ACTION_UP}. You can, however, call this 123 * for whichever events you desire. 124 * 125 * @param ev The MotionEvent you received and would like to track. 126 */ 127 public void addMovement(MotionEvent ev) { 128 final int N = ev.getHistorySize(); 129 final int pointerCount = ev.getPointerCount(); 130 int touchIndex = (mLastTouch + 1) % NUM_PAST; 131 for (int i=0; i<N; i++) { 132 for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { 133 mPastTime[id][touchIndex] = Long.MIN_VALUE; 134 } 135 for (int p = 0; p < pointerCount; p++) { 136 int id = ev.getPointerId(p); 137 mPastX[id][touchIndex] = ev.getHistoricalX(p, i); 138 mPastY[id][touchIndex] = ev.getHistoricalY(p, i); 139 mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i); 140 } 141 142 touchIndex = (touchIndex + 1) % NUM_PAST; 143 } 144 145 // During calculation any pointer values with a time of MIN_VALUE are treated 146 // as a break in input. Initialize all to MIN_VALUE for each new touch index. 147 for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { 148 mPastTime[id][touchIndex] = Long.MIN_VALUE; 149 } 150 final long time = ev.getEventTime(); 151 for (int p = 0; p < pointerCount; p++) { 152 int id = ev.getPointerId(p); 153 mPastX[id][touchIndex] = ev.getX(p); 154 mPastY[id][touchIndex] = ev.getY(p); 155 mPastTime[id][touchIndex] = time; 156 } 157 158 mLastTouch = touchIndex; 159 } 160 161 /** 162 * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum 163 * velocity of Float.MAX_VALUE. 164 * 165 * @see #computeCurrentVelocity(int, float) 166 */ 167 public void computeCurrentVelocity(int units) { 168 computeCurrentVelocity(units, Float.MAX_VALUE); 169 } 170 171 /** 172 * Compute the current velocity based on the points that have been 173 * collected. Only call this when you actually want to retrieve velocity 174 * information, as it is relatively expensive. You can then retrieve 175 * the velocity with {@link #getXVelocity()} and 176 * {@link #getYVelocity()}. 177 * 178 * @param units The units you would like the velocity in. A value of 1 179 * provides pixels per millisecond, 1000 provides pixels per second, etc. 180 * @param maxVelocity The maximum velocity that can be computed by this method. 181 * This value must be declared in the same unit as the units parameter. This value 182 * must be positive. 183 */ 184 public void computeCurrentVelocity(int units, float maxVelocity) { 185 for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) { 186 final float[] pastX = mPastX[pos]; 187 final float[] pastY = mPastY[pos]; 188 final long[] pastTime = mPastTime[pos]; 189 final int lastTouch = mLastTouch; 190 191 // find oldest acceptable time 192 int oldestTouch = lastTouch; 193 if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ? 194 final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; 195 int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; 196 while (pastTime[nextOldestTouch] >= acceptableTime && 197 nextOldestTouch != lastTouch) { 198 oldestTouch = nextOldestTouch; 199 nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; 200 } 201 } 202 203 // Kind-of stupid. 204 final float oldestX = pastX[oldestTouch]; 205 final float oldestY = pastY[oldestTouch]; 206 final long oldestTime = pastTime[oldestTouch]; 207 float accumX = 0; 208 float accumY = 0; 209 int N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; 210 // Skip the last received event, since it is probably pretty noisy. 211 if (N > 3) N--; 212 213 for (int i=1; i < N; i++) { 214 final int j = (oldestTouch + i) % NUM_PAST; 215 final int dur = (int)(pastTime[j] - oldestTime); 216 if (dur == 0) continue; 217 float dist = pastX[j] - oldestX; 218 float vel = (dist/dur) * units; // pixels/frame. 219 accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; 220 221 dist = pastY[j] - oldestY; 222 vel = (dist/dur) * units; // pixels/frame. 223 accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; 224 } 225 226 mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity) 227 : Math.min(accumX, maxVelocity); 228 mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity) 229 : Math.min(accumY, maxVelocity); 230 231 if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" 232 + mXVelocity + " N=" + N); 233 } 234 } 235 236 /** 237 * Retrieve the last computed X velocity. You must first call 238 * {@link #computeCurrentVelocity(int)} before calling this function. 239 * 240 * @return The previously computed X velocity. 241 */ 242 public float getXVelocity() { 243 return mXVelocity[0]; 244 } 245 246 /** 247 * Retrieve the last computed Y velocity. You must first call 248 * {@link #computeCurrentVelocity(int)} before calling this function. 249 * 250 * @return The previously computed Y velocity. 251 */ 252 public float getYVelocity() { 253 return mYVelocity[0]; 254 } 255 256 /** 257 * Retrieve the last computed X velocity. You must first call 258 * {@link #computeCurrentVelocity(int)} before calling this function. 259 * 260 * @param id Which pointer's velocity to return. 261 * @return The previously computed X velocity. 262 */ 263 public float getXVelocity(int id) { 264 return mXVelocity[id]; 265 } 266 267 /** 268 * Retrieve the last computed Y velocity. You must first call 269 * {@link #computeCurrentVelocity(int)} before calling this function. 270 * 271 * @param id Which pointer's velocity to return. 272 * @return The previously computed Y velocity. 273 */ 274 public float getYVelocity(int id) { 275 return mYVelocity[id]; 276 } 277} 278