VelocityTracker.java revision d65addb4c9e8d552907bc2720e6127e0259b8f09
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; 21 22/** 23 * Helper for tracking the velocity of touch events, for implementing 24 * flinging and other such gestures. Use {@link #obtain} to retrieve a 25 * new instance of the class when you are going to begin tracking, put 26 * the motion events you receive into it with {@link #addMovement(MotionEvent)}, 27 * and when you want to determine the velocity call 28 * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()} 29 * and {@link #getXVelocity()}. 30 */ 31public final class VelocityTracker { 32 static final String TAG = "VelocityTracker"; 33 static final boolean DEBUG = false; 34 static final boolean localLOGV = DEBUG || Config.LOGV; 35 36 static final int NUM_PAST = 10; 37 static final int LONGEST_PAST_TIME = 200; 38 39 static final VelocityTracker[] mPool = new VelocityTracker[1]; 40 41 final float mPastX[] = new float[NUM_PAST]; 42 final float mPastY[] = new float[NUM_PAST]; 43 final long mPastTime[] = new long[NUM_PAST]; 44 45 float mYVelocity; 46 float mXVelocity; 47 48 /** 49 * Retrieve a new VelocityTracker object to watch the velocity of a 50 * motion. Be sure to call {@link #recycle} when done. You should 51 * generally only maintain an active object while tracking a movement, 52 * so that the VelocityTracker can be re-used elsewhere. 53 * 54 * @return Returns a new VelocityTracker. 55 */ 56 static public VelocityTracker obtain() { 57 synchronized (mPool) { 58 VelocityTracker vt = mPool[0]; 59 if (vt != null) { 60 vt.clear(); 61 mPool[0] = null; 62 return vt; 63 } 64 return new VelocityTracker(); 65 } 66 } 67 68 /** 69 * Return a VelocityTracker object back to be re-used by others. You must 70 * not touch the object after calling this function. 71 */ 72 public void recycle() { 73 synchronized (mPool) { 74 mPool[0] = this; 75 } 76 } 77 78 private VelocityTracker() { 79 } 80 81 /** 82 * Reset the velocity tracker back to its initial state. 83 */ 84 public void clear() { 85 mPastTime[0] = 0; 86 } 87 88 /** 89 * Add a user's movement to the tracker. You should call this for the 90 * initial {@link MotionEvent#ACTION_DOWN}, the following 91 * {@link MotionEvent#ACTION_MOVE} events that you receive, and the 92 * final {@link MotionEvent#ACTION_UP}. You can, however, call this 93 * for whichever events you desire. 94 * 95 * @param ev The MotionEvent you received and would like to track. 96 */ 97 public void addMovement(MotionEvent ev) { 98 long time = ev.getEventTime(); 99 final int N = ev.getHistorySize(); 100 for (int i=0; i<N; i++) { 101 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), 102 ev.getHistoricalEventTime(i)); 103 } 104 addPoint(ev.getX(), ev.getY(), time); 105 } 106 107 private void addPoint(float x, float y, long time) { 108 int drop = -1; 109 int i; 110 if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time); 111 final long[] pastTime = mPastTime; 112 for (i=0; i<NUM_PAST; i++) { 113 if (pastTime[i] == 0) { 114 break; 115 } else if (pastTime[i] < time-LONGEST_PAST_TIME) { 116 if (localLOGV) Log.v(TAG, "Dropping past too old at " 117 + i + " time=" + pastTime[i]); 118 drop = i; 119 } 120 } 121 if (localLOGV) Log.v(TAG, "Add index: " + i); 122 if (i == NUM_PAST && drop < 0) { 123 drop = 0; 124 } 125 if (drop == i) drop--; 126 final float[] pastX = mPastX; 127 final float[] pastY = mPastY; 128 if (drop >= 0) { 129 if (localLOGV) Log.v(TAG, "Dropping up to #" + drop); 130 final int start = drop+1; 131 final int count = NUM_PAST-drop-1; 132 System.arraycopy(pastX, start, pastX, 0, count); 133 System.arraycopy(pastY, start, pastY, 0, count); 134 System.arraycopy(pastTime, start, pastTime, 0, count); 135 i -= (drop+1); 136 } 137 pastX[i] = x; 138 pastY[i] = y; 139 pastTime[i] = time; 140 i++; 141 if (i < NUM_PAST) { 142 pastTime[i] = 0; 143 } 144 } 145 146 /** 147 * Compute the current velocity based on the points that have been 148 * collected. Only call this when you actually want to retrieve velocity 149 * information, as it is relatively expensive. You can then retrieve 150 * the velocity with {@link #getXVelocity()} and 151 * {@link #getYVelocity()}. 152 * 153 * @param units The units you would like the velocity in. A value of 1 154 * provides pixels per millisecond, 1000 provides pixels per second, etc. 155 */ 156 public void computeCurrentVelocity(int units) { 157 final float[] pastX = mPastX; 158 final float[] pastY = mPastY; 159 final long[] pastTime = mPastTime; 160 161 // Kind-of stupid. 162 final float oldestX = pastX[0]; 163 final float oldestY = pastY[0]; 164 final long oldestTime = pastTime[0]; 165 float accumX = 0; 166 float accumY = 0; 167 int N=0; 168 while (N < NUM_PAST) { 169 if (pastTime[N] == 0) { 170 break; 171 } 172 N++; 173 } 174 // Skip the last received event, since it is probably pretty noisy. 175 if (N > 3) N--; 176 177 for (int i=1; i < N; i++) { 178 final int dur = (int)(pastTime[i] - oldestTime); 179 if (dur == 0) continue; 180 float dist = pastX[i] - oldestX; 181 float vel = (dist/dur) * units; // pixels/frame. 182 if (accumX == 0) accumX = vel; 183 else accumX = (accumX + vel) * .5f; 184 185 dist = pastY[i] - oldestY; 186 vel = (dist/dur) * units; // pixels/frame. 187 if (accumY == 0) accumY = vel; 188 else accumY = (accumY + vel) * .5f; 189 } 190 mXVelocity = accumX; 191 mYVelocity = accumY; 192 193 if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" 194 + mXVelocity + " N=" + N); 195 } 196 197 /** 198 * Retrieve the last computed X velocity. You must first call 199 * {@link #computeCurrentVelocity(int)} before calling this function. 200 * 201 * @return The previously computed X velocity. 202 */ 203 public float getXVelocity() { 204 return mXVelocity; 205 } 206 207 /** 208 * Retrieve the last computed Y velocity. You must first call 209 * {@link #computeCurrentVelocity(int)} before calling this function. 210 * 211 * @return The previously computed Y velocity. 212 */ 213 public float getYVelocity() { 214 return mYVelocity; 215 } 216} 217