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